From 40220a6f529478cdfc8988335b36bb419dd24feb Mon Sep 17 00:00:00 2001 From: kanarus Date: Tue, 4 Mar 2025 04:03:59 +0900 Subject: [PATCH 1/4] enhance(worker): Support wrangler.jsonc --- ohkami_macros/Cargo.toml | 14 ++- ohkami_macros/src/util.rs | 18 ++- ohkami_macros/src/worker.rs | 38 +----- ohkami_macros/src/worker/binding.rs | 167 ++++++++++++++------------- ohkami_macros/src/worker/meta.rs | 58 +++++----- ohkami_macros/src/worker/wrangler.rs | 35 ++++++ 6 files changed, 172 insertions(+), 158 deletions(-) create mode 100644 ohkami_macros/src/worker/wrangler.rs diff --git a/ohkami_macros/Cargo.toml b/ohkami_macros/Cargo.toml index 636359521..28ea6f079 100644 --- a/ohkami_macros/Cargo.toml +++ b/ohkami_macros/Cargo.toml @@ -19,14 +19,16 @@ license = { workspace = true } features = ["worker", "openapi"] [dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "2.0", features = ["full"] } -toml = { optional = true, version = "0.8", features = ["parse"], default-features = false } -serde_json = { optional = true, workspace = true } +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } +toml = { optional = true, version = "0.8", features = ["parse"], default-features = false } +jsonc-parser = { optional = true, version = "0.26", features = ["serde"] } +serde = { optional = true, workspace = true } +serde_json = { optional = true, workspace = true } [features] -worker = ["dep:toml", "dep:serde_json"] +worker = ["dep:toml", "dep:jsonc-parser", "dep:serde", "dep:serde_json"] openapi = [] ##### DEBUG ##### diff --git a/ohkami_macros/src/util.rs b/ohkami_macros/src/util.rs index 2ed0ee673..e83138435 100644 --- a/ohkami_macros/src/util.rs +++ b/ohkami_macros/src/util.rs @@ -69,15 +69,15 @@ pub(crate) fn extract_doc_comment(attrs: &[syn::Attribute]) -> Option { /// So, if found the file by simple reading of `file_path`, this returns the file, /// but if not found, this assumes a Cargo workspace and search all `workspace.members` /// to find one having file at `file_path`. -pub(crate) fn find_a_file_in_maybe_workspace(file_path: impl AsRef) -> Result { +pub(crate) fn find_file_at_package_or_workspace_root(file_path: impl AsRef) -> Result, io::Error> { let file_path: &Path = file_path.as_ref(); match File::open(file_path) { Ok(file) => { - Ok(file) + Ok(Some(file)) } Err(e) if matches!(e.kind(), ErrorKind::NotFound) => { - find_a_file_in_workspace(file_path) + find_file_at_workspace_root(file_path) } Err(e) => { Err(e) @@ -103,7 +103,7 @@ fn unescaped_doc_attr(raw_doc: String) -> String { } #[cfg(feature="worker")] -fn find_a_file_in_workspace(file_path: impl AsRef) -> Result { +fn find_file_at_workspace_root(file_path: impl AsRef) -> Result, io::Error> { let file_path: &Path = file_path.as_ref(); let cargo_toml: toml::Value = {use std::io::Read; @@ -120,8 +120,8 @@ fn find_a_file_in_workspace(file_path: impl AsRef) -> Result) -> Result { - Ok(file) + Ok(Some(file)) } (None, _) => { - Err(io::Error::new(ErrorKind::NotFound, format!( - "No workspace member having `{}` found.", file_path.display() - ))) + Ok(None) } (Some(_), false) => { Err(io::Error::new(ErrorKind::Other, format!( diff --git a/ohkami_macros/src/worker.rs b/ohkami_macros/src/worker.rs index 662bba50e..73a67695a 100644 --- a/ohkami_macros/src/worker.rs +++ b/ohkami_macros/src/worker.rs @@ -1,5 +1,6 @@ #![cfg(feature="worker")] +mod wrangler; mod meta; mod durable; mod binding; @@ -112,40 +113,13 @@ pub fn worker(args: TokenStream, ohkami_fn: TokenStream) -> Result Result { use self::binding::Binding; - fn callsite(msg: impl std::fmt::Display) -> Error { - Error::new(Span::call_site(), msg) - } - fn invalid_wrangler_toml() -> Error { - Error::new(Span::call_site(), "Invalid wrangler.toml") - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - let wrangler_toml: toml::Value = {use std::io::Read; - let mut file = util::find_a_file_in_maybe_workspace("wrangler.toml") - .map_err(|e| callsite(e.to_string()))?; - let mut buf = String::new(); - file.read_to_string(&mut buf) - .map_err(|_| callsite("wrangler.toml found but it's not readable"))?; - toml::from_str(&buf) - .map_err(|_| callsite("Failed to read wrangler.toml"))? + let bindings: Vec<(Ident, Binding)> = { + let env_name: Option = (!env_name.is_empty()) + .then(|| syn::parse2(env_name)) + .transpose()?; + Binding::collect_from_env(env_name)? }; - let env: &toml::Table = { - let top_level = wrangler_toml.as_table().ok_or_else(invalid_wrangler_toml)?; - let env_name: Option = (!env_name.is_empty()).then(|| syn::parse2(env_name)).transpose()?; - match env_name { - None => top_level, - Some(env) => top_level.get("env") - .and_then(|e| e.as_table()) - .and_then(|t| t.get(&env.to_string())) - .and_then(|e| e.as_table()) - .ok_or_else(|| callsite(format!("env `{env}` is not found in wrangler.toml")))? - } - }; - - let bindings: Vec<(Ident, Binding)> = Binding::collect_from_env(&env)?; - let bindings_struct: ItemStruct = syn::parse2(bindings_struct)?; { if !bindings_struct.generics.params.is_empty() { return Err(Error::new( diff --git a/ohkami_macros/src/worker/binding.rs b/ohkami_macros/src/worker/binding.rs index c860d986c..2e054b2b0 100644 --- a/ohkami_macros/src/worker/binding.rs +++ b/ohkami_macros/src/worker/binding.rs @@ -1,6 +1,6 @@ use proc_macro2::{TokenStream, Span}; -use quote::quote; use syn::{Ident, LitStr}; +use quote::quote; pub enum Binding { Variable(String), @@ -58,100 +58,107 @@ impl Binding { Self::DurableObject => from_env(quote! { durable_object(#name_str) }), } } +} - pub fn collect_from_env(env: &toml::Table) -> Result, syn::Error> { - fn invalid_wrangler_toml() -> syn::Error { - syn::Error::new( - Span::call_site(), - "Invalid wrangler.toml: a binding doesn't have `binding = \"...\"`, or some unexpected structure" - ) - } - - fn invalid_name(name: &str) -> syn::Error { - syn::Error::new( - Span::call_site(), - format!("Can't bind binding `{name}` into Rust struct field") - ) - } - - /////////////////////////////////////////////////////////////////////////////////////////// +#[derive(serde::Deserialize)] +struct EnvBindingCollection { + #[serde(flatten)] + root: BindingCollection, + #[serde(default)] + env: std::collections::BTreeMap, +} - fn get_field_as_ident(t: &toml::Table, field: &str) -> Result { - t.get(field) - .and_then(|b| b.as_str()) - .ok_or_else(invalid_wrangler_toml) - .and_then(|name| syn::parse_str::(name) - .map_err(|_| invalid_name(name)) - ) - } +#[derive(serde::Deserialize, Default)] +struct BindingCollection { + vars: Option>, + ai: Option, + d1_databases: Option>, + kv_namespaces: Option>, + r2_buckets: Option>, + services: Option>, + queues: Option, + durable_objects: Option, +} - fn binding_of(t: &toml::Table) -> Result { - get_field_as_ident(t, "binding") - } - fn name_of(t: &toml::Table) -> Result { - get_field_as_ident(t, "name") - } +#[derive(serde::Deserialize)] +struct BindingName { + binding: String, +} - fn table_array(a: &toml::value::Array) -> Result, syn::Error> { - a.iter() - .map(|v| v.as_table().ok_or_else(invalid_wrangler_toml)) - .collect::, _>>() - } +#[derive(serde::Deserialize)] +struct QueueProducers { + producers: Vec, +} - /////////////////////////////////////////////////////////////////////////////////////////// +#[derive(serde::Deserialize)] +struct BindingsArray { + bindings: Vec, +} - let mut bindings = Vec::new(); +impl Binding { + pub fn collect_from_env(env_name: Option) -> Result, syn::Error> { + let mut config = super::wrangler::parse_wrangler::() + .map_err(|e| syn::Error::new(Span::call_site(), e))?; + let config = match env_name.as_ref() { + None => config.root, + Some(name) => { + let config = config.env.get_mut(&name.to_string()) + .ok_or_else(|| syn::Error::new(name.span(), format!("env `{name}` is not found in wrangler config")))?; + std::mem::take(config) + } + }; - if let Some(toml::Value::Table(vars)) = env.get("vars") { - for (name, value) in vars { - let name = syn::parse_str(name).map_err(|_| invalid_name(name))?; - let value = value.as_str() - .ok_or_else(|| syn::Error::new( - Span::call_site(), - "`#[bindings]` doesn't support JSON values in `vars` binding" - ))? - .to_owned(); - bindings.push((name, Self::Variable(value))) + let mut collection = Vec::new(); + { + if let Some(vars) = config.vars { + for (name, value) in vars { + collection.push((name, Self::Variable(value))); + } } - } - if let Some(toml::Value::Table(ai)) = env.get("ai") { - bindings.push((binding_of(ai)?, Self::AI)) - } - if let Some(toml::Value::Array(d1_databases)) = env.get("d1_databases") { - for d1 in table_array(d1_databases)? { - bindings.push((binding_of(d1)?, Self::D1)) + if let Some(BindingName { binding }) = config.ai { + collection.push((binding, Self::AI)); } - } - if let Some(toml::Value::Array(kv_namespaces)) = env.get("kv_namespaces") { - for kv in table_array(kv_namespaces)? { - bindings.push((binding_of(kv)?, Self::KV)) + if let Some(d1_databases) = config.d1_databases { + for BindingName { binding } in d1_databases { + collection.push((binding, Self::D1)); + } } - } - if let Some(toml::Value::Array(r2_buckets)) = env.get("r2_buckets") { - for r2 in table_array(r2_buckets)? { - bindings.push((binding_of(r2)?, Self::R2)) + if let Some(kv_namespaces) = config.kv_namespaces { + for BindingName { binding } in kv_namespaces { + collection.push((binding, Self::KV)); + } } - } - if let Some(toml::Value::Array(services)) = env.get("services") { - for service in table_array(services)? { - bindings.push((binding_of(service)?, Self::Service)) + if let Some(r2_buckets) = config.r2_buckets { + for BindingName { binding } in r2_buckets { + collection.push((binding, Self::R2)); + } } - } - if let Some(toml::Value::Table(queues)) = env.get("queues") { - if let Some(toml::Value::Array(producers)) = queues.get("producers") { - for producer in table_array(producers)? { - bindings.push((binding_of(producer)?, Self::Queue)) + if let Some(services) = config.services { + for BindingName { binding } in services { + collection.push((binding, Self::Service)); } } - } - if let Some(toml::Value::Table(durable_objects)) = env.get("durable_objects") { - if let Some(toml::Value::Array(durable_object_bindings)) = durable_objects.get("bindings") { - for durable_object in table_array(durable_object_bindings)? { - bindings.push((name_of(durable_object)?, Self::DurableObject)) + if let Some(QueueProducers { producers }) = config.queues { + for BindingName { binding } in producers { + collection.push((binding, Self::Queue)); } } - } - - Ok(bindings) + if let Some(BindingsArray { bindings }) = config.durable_objects { + for BindingName { binding } in bindings { + collection.push((binding, Self::DurableObject)); + } + } + } + + collection + .into_iter() + .map(|(name, binding)| { + let name = syn::parse_str(&name).map_err(|_| syn::Error::new( + Span::call_site(), + format!("can't handle binding name `{name}` as a Rust identifier") + ))?; + Ok((name, binding)) + }) + .collect() } } diff --git a/ohkami_macros/src/worker/meta.rs b/ohkami_macros/src/worker/meta.rs index 7ce35cbae..17a0584e4 100644 --- a/ohkami_macros/src/worker/meta.rs +++ b/ohkami_macros/src/worker/meta.rs @@ -28,8 +28,9 @@ const _: (/* TryDefault */) = { impl TryDefault for WorkerMeta { fn try_default() -> syn::Result { let package_json = {use std::io::Read; - let mut file = util::find_a_file_in_maybe_workspace("package.json") - .map_err(|e| syn::Error::new(Span::call_site(), e.to_string()))?; + let mut file = util::find_file_at_package_or_workspace_root("package.json") + .map_err(|e| syn::Error::new(Span::call_site(), e.to_string()))? + .ok_or_else(|| syn::Error::new(Span::call_site(), "`package.json` is not found"))?; let mut buf = String::new(); file.read_to_string(&mut buf) .map_err(|e| syn::Error::new(Span::call_site(), e.to_string()))?; @@ -38,19 +39,22 @@ const _: (/* TryDefault */) = { .expect("invalid package.json") }; - let wrangler_toml = {use std::io::Read; - let mut file = util::find_a_file_in_maybe_workspace("wrangler.toml") - .map_err(|e| syn::Error::new(Span::call_site(), e.to_string()))?; - let mut buf = String::new(); - file.read_to_string(&mut buf) - .map_err(|e| syn::Error::new(Span::call_site(), e.to_string()))?; - toml::from_str(&buf).ok() - .and_then(|t| match t {toml::Value::Table(t) => Some(t), _ => None}) - .expect("invalid wrangler.toml") + let wrangler_config = { + #[derive(serde::Deserialize)] + struct RouteConfig { + routes: Option>, + route: Option, + } + #[derive(serde::Deserialize)] + struct RoutePattern { + pattern: String, + } + + super::wrangler::parse_wrangler::() + .expect("invalid or not found wrangler config") }; let title = LitStr::new(package_json["name"].as_str().unwrap(), Span::call_site()); - let version = LitStr::new(package_json["version"].as_str().unwrap(), Span::call_site()); let mut servers = vec![ @@ -60,35 +64,29 @@ const _: (/* TryDefault */) = { variables: None, } ]; - if let Some(routes) = wrangler_toml.get("routes").and_then(|r| r.as_array()) { + fn to_url(route_pattern: &str) -> LitStr { + if route_pattern.contains("://") { + LitStr::new(route_pattern, Span::call_site()) + } else { + LitStr::new(&format!("https://{route_pattern}"), Span::call_site()) + } + } + if let Some(routes) = wrangler_config.routes { for route in routes { - let pattern = route - .as_table() - .and_then(|r| r.get("pattern")) - .and_then(|p| p.as_str()) - .expect("invalid `routes` of wrangler.toml") - .trim_end_matches(&['/', '*']); servers.push(Server { - url: to_url(pattern), + url: to_url(route.pattern.trim_end_matches(&['/', '*'])), description: None, variables: None, }); } - } else if let Some(route) = wrangler_toml.get("route").and_then(|r| r.as_str()) { - let route = route.trim_end_matches(&['/', '*']); + } else if let Some(route) = wrangler_config.route { servers.push(Server { - url: to_url(route), + url: to_url(route.trim_end_matches(&['/', '*'])), description: None, variables: None, }); }; - fn to_url(route_pattern: &str) -> LitStr { - if route_pattern.contains("://") { - LitStr::new(route_pattern, Span::call_site()) - } else { - LitStr::new(&format!("https://{route_pattern}"), Span::call_site()) - } - } + if servers.len() == 1 + 1 { servers[1].description = Some(LitStr::new("production", Span::call_site())); } diff --git a/ohkami_macros/src/worker/wrangler.rs b/ohkami_macros/src/worker/wrangler.rs new file mode 100644 index 000000000..c61e46aa8 --- /dev/null +++ b/ohkami_macros/src/worker/wrangler.rs @@ -0,0 +1,35 @@ +use crate::util; +use std::io::{self, Read}; + +pub fn parse_wrangler() -> Result { + fn parse_error(filename: &str, e: impl std::fmt::Display) -> io::Error { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to parse `{filename}`: `{e}`") + ) + } + + let mut buf = String::new(); + + if let Some(mut wrangler_toml) = util::find_file_at_package_or_workspace_root("wrangler.toml")? { + wrangler_toml.read_to_string(&mut buf)?; + let config = toml::from_str(&buf) + .map_err(|e| parse_error("wrangler.toml", e))?; + Ok(config) + + } else if let Some(mut wrangler_jsonc) = util::find_file_at_package_or_workspace_root("wrangler.jsonc")? { + wrangler_jsonc.read_to_string(&mut buf)?; + let config = jsonc_parser::parse_to_serde_value(&buf, &Default::default()) + .map_err(|e| parse_error("wrangler.jsonc", e))? + .ok_or_else(|| parse_error("wrangler.jsonc", "invalid `.jsonc`"))?; + let config = serde_json::from_value(config) + .map_err(|e| parse_error("wrangler.jsonc", e))?; + Ok(config) + + } else { + Err(io::Error::new( + io::ErrorKind::NotFound, + "neither `wrangler.toml` nor `wrangler.jsonc` is found at package or workspace root" + )) + } +} \ No newline at end of file From 40bd8cb4c0f459e9aa6500b93cfc02f1d289a6ee Mon Sep 17 00:00:00 2001 From: kanarus Date: Tue, 4 Mar 2025 17:15:10 +0900 Subject: [PATCH 2/4] pass regression tests (toml / by samples/test.sh) --- ohkami_macros/src/util.rs | 22 +++++----- ohkami_macros/src/worker/binding.rs | 63 +++++++++++++++++----------- ohkami_macros/src/worker/wrangler.rs | 55 ++++++++++++++---------- 3 files changed, 83 insertions(+), 57 deletions(-) diff --git a/ohkami_macros/src/util.rs b/ohkami_macros/src/util.rs index e83138435..cbc3195da 100644 --- a/ohkami_macros/src/util.rs +++ b/ohkami_macros/src/util.rs @@ -113,16 +113,18 @@ fn find_file_at_workspace_root(file_path: impl AsRef) -> Result Option<&toml::value::Array> { + cargo_toml + .as_table()? + .get("workspace")? + .as_table()? + .get("members")? + .as_array() + } + + let Some(workspace_members) = get_workspace_members(&cargo_toml) else { + return Ok(None) + }; let mut matching_files = Vec::with_capacity(1); for member in workspace_members { diff --git a/ohkami_macros/src/worker/binding.rs b/ohkami_macros/src/worker/binding.rs index 2e054b2b0..51baf48e9 100644 --- a/ohkami_macros/src/worker/binding.rs +++ b/ohkami_macros/src/worker/binding.rs @@ -60,34 +60,42 @@ impl Binding { } } -#[derive(serde::Deserialize)] -struct EnvBindingCollection { - #[serde(flatten)] - root: BindingCollection, - #[serde(default)] - env: std::collections::BTreeMap, -} - #[derive(serde::Deserialize, Default)] -struct BindingCollection { +struct EnvBindingCollection { vars: Option>, - ai: Option, - d1_databases: Option>, - kv_namespaces: Option>, - r2_buckets: Option>, - services: Option>, + ai: Option, + d1_databases: Option>, + kv_namespaces: Option>, + r2_buckets: Option>, + services: Option>, queues: Option, durable_objects: Option, + // #[serde(flatten)] + // root: BindingCollection, + #[serde(default)] + env: std::collections::BTreeMap, } +// #[derive(serde::Deserialize, Default)] +// struct BindingCollection { +// vars: Option>, +// ai: Option, +// d1_databases: Option>, +// kv_namespaces: Option>, +// r2_buckets: Option>, +// services: Option>, +// queues: Option, +// durable_objects: Option, +// } + #[derive(serde::Deserialize)] -struct BindingName { +struct BindingDeclare { binding: String, } #[derive(serde::Deserialize)] struct QueueProducers { - producers: Vec, + producers: Vec, } #[derive(serde::Deserialize)] @@ -95,12 +103,17 @@ struct BindingsArray { bindings: Vec, } +#[derive(serde::Deserialize)] +struct BindingName { + name: String, +} + impl Binding { pub fn collect_from_env(env_name: Option) -> Result, syn::Error> { let mut config = super::wrangler::parse_wrangler::() .map_err(|e| syn::Error::new(Span::call_site(), e))?; let config = match env_name.as_ref() { - None => config.root, + None => config, Some(name) => { let config = config.env.get_mut(&name.to_string()) .ok_or_else(|| syn::Error::new(name.span(), format!("env `{name}` is not found in wrangler config")))?; @@ -115,37 +128,37 @@ impl Binding { collection.push((name, Self::Variable(value))); } } - if let Some(BindingName { binding }) = config.ai { + if let Some(BindingDeclare { binding }) = config.ai { collection.push((binding, Self::AI)); } if let Some(d1_databases) = config.d1_databases { - for BindingName { binding } in d1_databases { + for BindingDeclare { binding } in d1_databases { collection.push((binding, Self::D1)); } } if let Some(kv_namespaces) = config.kv_namespaces { - for BindingName { binding } in kv_namespaces { + for BindingDeclare { binding } in kv_namespaces { collection.push((binding, Self::KV)); } } if let Some(r2_buckets) = config.r2_buckets { - for BindingName { binding } in r2_buckets { + for BindingDeclare { binding } in r2_buckets { collection.push((binding, Self::R2)); } } if let Some(services) = config.services { - for BindingName { binding } in services { + for BindingDeclare { binding } in services { collection.push((binding, Self::Service)); } } if let Some(QueueProducers { producers }) = config.queues { - for BindingName { binding } in producers { + for BindingDeclare { binding } in producers { collection.push((binding, Self::Queue)); } } if let Some(BindingsArray { bindings }) = config.durable_objects { - for BindingName { binding } in bindings { - collection.push((binding, Self::DurableObject)); + for BindingName { name } in bindings { + collection.push((name, Self::DurableObject)); } } } diff --git a/ohkami_macros/src/worker/wrangler.rs b/ohkami_macros/src/worker/wrangler.rs index c61e46aa8..a8aa65118 100644 --- a/ohkami_macros/src/worker/wrangler.rs +++ b/ohkami_macros/src/worker/wrangler.rs @@ -2,34 +2,45 @@ use crate::util; use std::io::{self, Read}; pub fn parse_wrangler() -> Result { - fn parse_error(filename: &str, e: impl std::fmt::Display) -> io::Error { + fn parse_error(e: impl std::fmt::Display) -> io::Error { io::Error::new( io::ErrorKind::InvalidData, - format!("failed to parse `{filename}`: `{e}`") + format!("failed to parse wrangler config: `{e}`") ) } let mut buf = String::new(); - if let Some(mut wrangler_toml) = util::find_file_at_package_or_workspace_root("wrangler.toml")? { - wrangler_toml.read_to_string(&mut buf)?; - let config = toml::from_str(&buf) - .map_err(|e| parse_error("wrangler.toml", e))?; - Ok(config) - - } else if let Some(mut wrangler_jsonc) = util::find_file_at_package_or_workspace_root("wrangler.jsonc")? { - wrangler_jsonc.read_to_string(&mut buf)?; - let config = jsonc_parser::parse_to_serde_value(&buf, &Default::default()) - .map_err(|e| parse_error("wrangler.jsonc", e))? - .ok_or_else(|| parse_error("wrangler.jsonc", "invalid `.jsonc`"))?; - let config = serde_json::from_value(config) - .map_err(|e| parse_error("wrangler.jsonc", e))?; - Ok(config) - - } else { - Err(io::Error::new( - io::ErrorKind::NotFound, - "neither `wrangler.toml` nor `wrangler.jsonc` is found at package or workspace root" - )) + match ( + util::find_file_at_package_or_workspace_root("wrangler.toml")?, + util::find_file_at_package_or_workspace_root("wrangler.jsonc")? + ) { + (Some(_), Some(_)) => { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "both `wrangler.toml` and `wrangler.jsonc` is found !" + )) + } + (None, None) => { + Err(io::Error::new( + io::ErrorKind::NotFound, + "neither `wrangler.toml` nor `wrangler.jsonc` is found at package or workspace root" + )) + } + (Some(mut wrangler_toml), None) => { + wrangler_toml.read_to_string(&mut buf)?; + let config = toml::from_str(&buf) + .map_err(parse_error)?; + Ok(config) + } + (None, Some(mut wrangler_jsonc)) => { + wrangler_jsonc.read_to_string(&mut buf)?; + let config = jsonc_parser::parse_to_serde_value(&buf, &Default::default()) + .map_err(parse_error)? + .ok_or_else(|| parse_error("invalid `.jsonc`"))?; + let config = serde_json::from_value(config) + .map_err(parse_error)?; + Ok(config) + } } } \ No newline at end of file From 1bafc8fd2a8adce429a90244c7beb208445fa402 Mon Sep 17 00:00:00 2001 From: kanarus Date: Tue, 4 Mar 2025 17:28:00 +0900 Subject: [PATCH 3/4] add `samples/worker-bindings-jsonc` & pass test ( compiles / by samples/test.sh ) --- samples/test.sh | 10 +- .../worker-bindings-jsonc/.cargo/config.toml | 2 + samples/worker-bindings-jsonc/Cargo.toml | 13 +++ samples/worker-bindings-jsonc/src/lib.rs | 75 +++++++++++++++ samples/worker-bindings-jsonc/wrangler.jsonc | 91 +++++++++++++++++++ samples/worker-bindings/Cargo.toml | 2 +- 6 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 samples/worker-bindings-jsonc/.cargo/config.toml create mode 100644 samples/worker-bindings-jsonc/Cargo.toml create mode 100644 samples/worker-bindings-jsonc/src/lib.rs create mode 100644 samples/worker-bindings-jsonc/wrangler.jsonc diff --git a/samples/test.sh b/samples/test.sh index 778c9e77a..a96653e95 100755 --- a/samples/test.sh +++ b/samples/test.sh @@ -62,13 +62,17 @@ cd $SAMPLES/worker-bindings && \ node dummy_env_test.js test $? -ne 0 && exit 157 || : -cd $SAMPLES/worker-durable-websocket && \ +cd $SAMPLES/worker-bindings-jsonc && \ cargo check test $? -ne 0 && exit 158 || : +cd $SAMPLES/worker-durable-websocket && \ + cargo check +test $? -ne 0 && exit 159 || : + cd $SAMPLES/worker-with-global-bindings && \ npm run openapi -test $? -ne 0 && exit 159 || : +test $? -ne 0 && exit 160 || : cd $SAMPLES/worker-with-openapi && \ cp wrangler.toml.sample wrangler.toml && \ @@ -84,4 +88,4 @@ cd $SAMPLES/worker-with-openapi && \ diff openapi.json tmp.json \ ; (test -f tmp.json && rm tmp.json) \ || :) -test $? -ne 0 && exit 160 || : +test $? -ne 0 && exit 161 || : diff --git a/samples/worker-bindings-jsonc/.cargo/config.toml b/samples/worker-bindings-jsonc/.cargo/config.toml new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/samples/worker-bindings-jsonc/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/samples/worker-bindings-jsonc/Cargo.toml b/samples/worker-bindings-jsonc/Cargo.toml new file mode 100644 index 000000000..e6121f015 --- /dev/null +++ b/samples/worker-bindings-jsonc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "worker-bindings-test-jsonc" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +# set `default-features = false` to assure "DEBUG" feature be off even when DEBUGing `../ohkami` +ohkami = { path = "../../ohkami", default-features = false, features = ["rt_worker"] } +worker = { version = "0.5", features = ["queue", "d1"] } +console_error_panic_hook = "0.1" diff --git a/samples/worker-bindings-jsonc/src/lib.rs b/samples/worker-bindings-jsonc/src/lib.rs new file mode 100644 index 000000000..d20fafe6a --- /dev/null +++ b/samples/worker-bindings-jsonc/src/lib.rs @@ -0,0 +1,75 @@ +/// almost the same as `worker-bindings`, but using `wrangler.jsonc` instead of toml + +use ohkami::bindings; + +#[bindings] +struct AutoBindings; + +#[bindings] +struct ManualBindings { + /* automatically `#[allow(unused)]` */ + VARIABLE_1: bindings::Var, + + #[allow(unused)] + DB: bindings::D1, + + #[allow(unused)] + MY_KVSTORE: bindings::KV, +} + +macro_rules! static_assert_eq_str { + ($left:expr, $right:literal) => { + const _: [(); true as usize] = [(); 'eq: { + let (left, right) = ($left.as_bytes(), $right.as_bytes()); + if left.len() != right.len() { + break 'eq false + } + let mut i = 0; while i < left.len() { + if left[i] != right[i] { + break 'eq false + } + i += 1; + } + true + } as usize]; + }; +} + +fn __test_auto_bindings__(bindings: AutoBindings) { + fn assert_send_sync() {} + assert_send_sync::(); + + static_assert_eq_str!(AutoBindings::VARIABLE_1, "hoge"); + static_assert_eq_str!(AutoBindings::VARIABLE_2, "super fun"); + + let _: worker::Ai = bindings.INTELIGENT; + + let _: worker::D1Database = bindings.DB; + + let _: worker::kv::KvStore = bindings.MY_KVSTORE; + + let _: worker::Bucket = bindings.MY_BUCKET; + + let _: worker::Fetcher = bindings.S; + + let _: worker::Queue = bindings.MY_QUEUE; + + let _: worker::ObjectNamespace = bindings.RATE_LIMITER; +} + +fn __test_manual_bindings__(bindings: ManualBindings) { + fn assert_send_sync() {} + assert_send_sync::(); + + static_assert_eq_str!(ManualBindings::VARIABLE_1, "hoge"); + + let _: worker::D1Database = bindings.DB; + + let _: worker::kv::KvStore = bindings.MY_KVSTORE; +} + +fn __test_bindings_new__(env: &worker::Env) -> Result<(), worker::Error> { + let _: AutoBindings = AutoBindings::new(env)?; + let _: ManualBindings = ManualBindings::new(env)?; + Ok(()) +} diff --git a/samples/worker-bindings-jsonc/wrangler.jsonc b/samples/worker-bindings-jsonc/wrangler.jsonc new file mode 100644 index 000000000..c45f884aa --- /dev/null +++ b/samples/worker-bindings-jsonc/wrangler.jsonc @@ -0,0 +1,91 @@ +/* + ../worker-bindings/wrangler.toml : + + ```toml + name = "worker-bindings-test" + + [vars] + VARIABLE_1 = "hoge" + VARIABLE_2 = "super fun" + + [ai] + binding = "INTELIGENT" + + [[d1_databases]] + binding = "DB" + database_name = "db" + database_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + + [[kv_namespaces]] + binding = "MY_KVSTORE" + id = "" + + [[r2_buckets]] + binding = 'MY_BUCKET' + bucket_name = '' + + [[services]] + binding = "S" + service = "" + + [[queues.producers]] + queue = "my-queue" + binding = "MY_QUEUE" + + [[durable_objects.bindings]] + name = "RATE_LIMITER" + class_name = "RateLimiter" + ``` +*/ + +{ + "name": "worker-bindings-test-jsonc", + "vars": { + "VARIABLE_1": "hoge", + "VARIABLE_2": "super fun" + }, + "ai": { + "binding": "INTELIGENT" + }, + "d1_databases": [ + { + "binding": "DB", + "database_name": "db", + "database_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + } + ], + "kv_namespaces": [ + { + "binding": "MY_KVSTORE", + "id": "" + } + ], + "r2_buckets": [ + { + "binding": "MY_BUCKET", + "bucket_name": "" + } + ], + "services": [ + { + "binding": "S", + "service": "" + } + ], + "queues": { + "producers": [ + { + "binding": "MY_QUEUE", + "queue": "my-queue" + } + ] + }, + "durable_objects": { + "bindings": [ + { + "name": "RATE_LIMITER", + "class_name": "RateLimiter" + } + ] + } +} diff --git a/samples/worker-bindings/Cargo.toml b/samples/worker-bindings/Cargo.toml index 45e8d5064..1ffaca9be 100644 --- a/samples/worker-bindings/Cargo.toml +++ b/samples/worker-bindings/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "worker-with-openapi" +name = "worker-bindings-test-toml" version = "0.1.0" edition = "2024" From 6a9125dfc6836b036907107e9dbcb9d99b84e23e Mon Sep 17 00:00:00 2001 From: kanarus Date: Tue, 4 Mar 2025 17:41:52 +0900 Subject: [PATCH 4/4] fix test --- samples/worker-bindings/Cargo.toml | 2 +- samples/worker-bindings/dummy_env_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/worker-bindings/Cargo.toml b/samples/worker-bindings/Cargo.toml index 1ffaca9be..98ad24ef4 100644 --- a/samples/worker-bindings/Cargo.toml +++ b/samples/worker-bindings/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "worker-bindings-test-toml" +name = "worker-bindings-test" version = "0.1.0" edition = "2024" diff --git a/samples/worker-bindings/dummy_env_test.js b/samples/worker-bindings/dummy_env_test.js index db6a2915c..b5abf48e3 100644 --- a/samples/worker-bindings/dummy_env_test.js +++ b/samples/worker-bindings/dummy_env_test.js @@ -3,7 +3,7 @@ import { join } from 'node:path'; import { cwd, exit } from 'node:process'; -const wasmpack_js = await import(join(cwd(), `pkg`, `worker_with_openapi.js`)); +const wasmpack_js = await import(join(cwd(), `pkg`, `worker_bindings_test.js`)); if (!wasmpack_js) { exit("wasmpack_js is not found") }