Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ rand = { version = "0.9.0", features = ["small_rng"] }
reqwest = "0.12.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_json5 = "0.2.1"
serde_with = { version = "3.12", features = ["chrono"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
Expand Down
30 changes: 23 additions & 7 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
pub(crate) use file::File;
pub(crate) use manifest::application_manifest;
pub(crate) use session::{Session, SessionFile, UserCreds};
pub(crate) use wrangler::{JsonWranglerFile, TomlWranglerFile};
pub(crate) use wrangler::{JsonWranglerFile, JsoncWranglerFile, TomlWranglerFile};

use manifest::{JsonManifest, Manifest, TomlManifest};
use wrangler::Wrangler;
Expand Down Expand Up @@ -77,15 +77,17 @@ impl FileSystem {
Ok(manifest_box)
}

/// Load the wrangler configuration file, looking for both TOML and JSON formats
/// Load the wrangler configuration file, looking for TOML, JSON, and JSONC formats
pub fn wrangler_config(&self) -> Result<Wrangler, WranglerMissing> {
// Attempt to load a TOML wrangler file. Fallback to JSON.
// Attempt to load a TOML wrangler file. Fallback to JSON, then JSONC.
let toml_wrangler = self.load_file(TomlWranglerFile);
let json_wrangler = self.load_file(JsonWranglerFile);
let wrangler_config = match (toml_wrangler, json_wrangler) {
(Ok(wrangler), _) => wrangler,
(Err(_), Ok(wrangler)) => wrangler,
(Err(_), Err(_)) => return Err(WranglerMissing),
let jsonc_wrangler = self.load_file(JsoncWranglerFile);
let wrangler_config = match (toml_wrangler, json_wrangler, jsonc_wrangler) {
(Ok(wrangler), _, _) => wrangler,
(Err(_), Ok(wrangler), _) => wrangler,
(Err(_), Err(_), Ok(wrangler)) => wrangler,
(Err(_), Err(_), Err(_)) => return Err(WranglerMissing),
};
Ok(wrangler_config)
}
Expand Down Expand Up @@ -119,6 +121,7 @@ impl FileSystem {
match F::EXTENSION {
"toml" => self.read_toml_file(file),
"json" => self.read_json_file(file),
"jsonc" => self.read_jsonc_file(file),
_ => Err(miette!(
"Extension unknown! Internal error. Please file this error as a bug."
)),
Expand Down Expand Up @@ -150,6 +153,18 @@ impl FileSystem {
Ok(document)
}

/// Open the file and deserialize it with serde (JSONC with comments support).
fn read_jsonc_file<F: File>(&self, file: F) -> Result<F::Data> {
// • Get the path to the file.
let path = file.path(self)?;
// • Open it as a byte stream, then deserialize those bytes.
let mut buffer = String::new();
let mut file = std::fs::File::open(path).into_diagnostic()?;
file.read_to_string(&mut buffer).into_diagnostic()?;
let document = serde_json5::from_str(&buffer).into_diagnostic()?;
Ok(document)
}

/// Store the file, using its canonical path.
pub(crate) fn save_file<F: File>(&self, file: &F, blob: &F::Data) -> Result<()> {
// • Get the path to the file.
Expand All @@ -159,6 +174,7 @@ impl FileSystem {
let marshalled = match F::EXTENSION {
"toml" => toml::to_string_pretty(blob).into_diagnostic()?,
"json" => serde_json::to_string_pretty(blob).into_diagnostic()?,
"jsonc" => serde_json5::to_string(blob).into_diagnostic()?,
_ => {
return Err(miette!(
"Extension unknown! Internal error. Please file this error as a bug."
Expand Down
75 changes: 74 additions & 1 deletion src/fs/wrangler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub struct Wrangler {
}

const WRANGLER_PREFIX: &str = "wrangler";
const WRANGLER_EXTENSIONS: [&str; 2] = ["toml", "json"];
const WRANGLER_EXTENSIONS: [&str; 3] = ["toml", "json", "jsonc"];

/// A TOML formatted wrangler file
pub struct TomlWranglerFile;
Expand All @@ -116,6 +116,16 @@ impl StaticFile for JsonWranglerFile {
const EXTENSION: &'static str = WRANGLER_EXTENSIONS[1];
}

/// A JSONC formatted wrangler file
pub struct JsoncWranglerFile;

impl StaticFile for JsoncWranglerFile {
type Data = Wrangler;
const DIR: DirectoryType = DirectoryType::ApplicationRoot;
const NAME: &'static str = WRANGLER_PREFIX;
const EXTENSION: &'static str = WRANGLER_EXTENSIONS[2];
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -188,4 +198,67 @@ mod tests {
let _deserialized: Wrangler =
toml::from_str(&toml_str).expect("Failed to deserialize from TOML");
}

#[test]
fn test_wrangler_serde_jsonc() {
const RAW_JSONC: &str = r#"/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "multitool-quickstart",
"account_id": "986dc7f2976d17c6205288a7a946ef6a",
"main": "src/index.js",
"compatibility_date": "2025-11-06",
"observability": {
"enabled": true
}
/**
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" }
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/
// "vars": { "MY_VARIABLE": "production_value" }
/**
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/binding/
*/
// "assets": { "directory": "./public/", "binding": "ASSETS" }
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
}"#;

let observed: Wrangler = serde_json5::from_str(RAW_JSONC).expect("Failed to parse JSONC");

assert_eq!(observed.name, "multitool-quickstart");
assert_eq!(
observed.account_id,
Some("986dc7f2976d17c6205288a7a946ef6a".to_string())
);
assert_eq!(observed.main, "src/index.js");
assert_eq!(observed.compatibility_date, Some("2025-11-06".to_string()));
assert!(observed.observability.is_some());
if let Some(observability) = observed.observability {
assert!(observability.enabled);
assert_eq!(observability.head_sampling_rate, None);
}
}
}
Loading