From 3db375d5dba2b7e825a2c700341fc4d28a3a80f4 Mon Sep 17 00:00:00 2001 From: Kinflou Date: Wed, 2 Sep 2026 15:47:21 +0800 Subject: [PATCH] feat(generate): comline.toml default_framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `[generate] default_framing` (and per-`[[generate.target]]` override) names the wire framing protocols use when they don't pick one with `@framing`. Resolved `[[target]]` → `[generate]` → unset, then handed to the generator on `GenRequest.default_framing`. No flag / env override — it shapes the generated API, it's not a per-run knob. Rev bumps: comline-codegen → generation c70336d, comline-codegen-rust → comline-rust 85fd967, comline-codegen-typescript → comline-typescript f72bc3e (kept in lockstep so the build resolves one comline-codegen). tests: - gen_config: section value reaches a declared target; `[[target]]` wins; absent ⇒ None - tests/cli/generate: `default_framing = "jsonrpc"` in comline.toml makes the generated `chat.rs` emit the JSON-RPC stack; without it, datagram --- Cargo.lock | 6 ++--- Cargo.toml | 6 ++--- src/commands/generate.rs | 1 + src/commands/new.rs | 1 + src/gen_config.rs | 52 ++++++++++++++++++++++++++++++++++++++++ tests/cli/generate.rs | 40 +++++++++++++++++++++++++++++++ 6 files changed, 100 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aba8563..c34b51c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,7 +389,7 @@ dependencies = [ [[package]] name = "comline-codegen" version = "0.1.0" -source = "git+https://github.com/ComlineProject/generation?rev=1f8c5e290eba72a578860ebb6dc33453a2f0726b#1f8c5e290eba72a578860ebb6dc33453a2f0726b" +source = "git+https://github.com/ComlineProject/generation?rev=c70336dcc9485cea2c99b0f1db3789e420deaeda#c70336dcc9485cea2c99b0f1db3789e420deaeda" dependencies = [ "comline-core", "eyre", @@ -398,7 +398,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=a0ade3bceeb51876be7565601b3b9c9c3d807196#a0ade3bceeb51876be7565601b3b9c9c3d807196" +source = "git+https://github.com/ComlineProject/comline-rust?rev=85fd9670fae794f043d28ada415dca7ed090d713#85fd9670fae794f043d28ada415dca7ed090d713" dependencies = [ "comline-codegen", "comline-core", @@ -408,7 +408,7 @@ dependencies = [ [[package]] name = "comline-codegen-typescript" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-typescript?rev=86fc4eb586529ef92041f8dd3c46ff9340103411#86fc4eb586529ef92041f8dd3c46ff9340103411" +source = "git+https://github.com/ComlineProject/comline-typescript?rev=f72bc3e336ebadbe062751afc0f2430fd216e28c#f72bc3e336ebadbe062751afc0f2430fd216e28c" dependencies = [ "comline-codegen", "comline-core", diff --git a/Cargo.toml b/Cargo.toml index 216e0b4..a7001cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,9 +30,9 @@ comline-core = { git = "https://github.com/ComlineProject/core", rev = "47ac5f10 # pin (below) — `codegen/`'s content is identical at this rev and the later # `generation` tip (only `conformance/`, which this crate never touches, # differs), so matching it here keeps one `comline-codegen` in the tree. -comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "1f8c5e290eba72a578860ebb6dc33453a2f0726b" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "a0ade3bceeb51876be7565601b3b9c9c3d807196", optional = true } -comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "86fc4eb586529ef92041f8dd3c46ff9340103411", optional = true } +comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "c70336dcc9485cea2c99b0f1db3789e420deaeda" } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "85fd9670fae794f043d28ada415dca7ed090d713", optional = true } +comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "f72bc3e336ebadbe062751afc0f2430fd216e28c", optional = true } [features] # Which language generators are compiled in. Drop one to shed its whole diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 92af12f..91b5b5c 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -168,6 +168,7 @@ fn generate_once(work_dir: &Path, overrides: &Overrides) -> Result<()> { gv.package_version.clone() }, }, + default_framing: t.default_framing.clone(), }; let files = generator(&request).map_err(|e| miette!("`{}` generator: {e}", t.language))?; diff --git a/src/commands/new.rs b/src/commands/new.rs index f96159d..df2b1d4 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -40,6 +40,7 @@ const COMLINE_TOML: &str = "\ # layout = \"{{language}}/{{namespace}}.{{ext}}\" # path under the root # mode = \"code\" # code | lib | dylib (only `code` today) # package_versions = \"latest\" # latest | all | [\"0.3.0\", \"0.4.0\"] +# default_framing = \"jsonrpc\" # wire framing for protocols with no `@framing` (else datagram) # # [[generate.target]] # language = \"rust\" diff --git a/src/gen_config.rs b/src/gen_config.rs index ea2e670..4ad3068 100644 --- a/src/gen_config.rs +++ b/src/gen_config.rs @@ -78,6 +78,11 @@ pub struct Generate { pub mode: Option, /// Version selection shared by every target unless overridden. pub package_versions: Option, + /// Fallback wire framing for protocols that do not pick one with `@framing`, + /// shared by every target unless overridden. A generator-recognised name + /// (`"jsonrpc"`, `"datagram"`, …); an unknown one leaves the generator's + /// built-in default in place. + pub default_framing: Option, /// `[[generate.target]]` blocks. #[serde(rename = "target")] pub targets: Vec, @@ -97,6 +102,7 @@ pub struct Target { pub layout: Option, pub mode: Option, pub package_versions: Option, + pub default_framing: Option, } /// A non-empty `COMLINE_GENERATE_*` env var, if set. @@ -145,6 +151,8 @@ pub struct ResolvedTarget { pub layout: String, pub mode: String, pub versions: VersionSpec, + /// Fallback framing handed to the generator; `None` ⇒ its built-in default. + pub default_framing: Option, } /// Variables a `layout` template can reference. @@ -209,6 +217,7 @@ pub fn resolve( layout: None, mode: None, package_versions: None, + default_framing: None, }) .collect() } else { @@ -305,6 +314,14 @@ pub fn resolve( .or_else(|| cfg.generate.package_versions.clone()) .unwrap_or_default(); + // `[[target]]` wins over `[generate]`; `None` at both ⇒ the generator's + // built-in default. No flag / env override — schema-shaping config, not + // a per-run knob. + let default_framing = t + .default_framing + .clone() + .or_else(|| cfg.generate.default_framing.clone()); + resolved.push(ResolvedTarget { language: t.language.clone(), lang_version, @@ -312,6 +329,7 @@ pub fn resolve( layout, mode, versions, + default_framing, }); } @@ -366,4 +384,38 @@ mod tests { let resolved = resolve(&cfg, &declared, Path::new("/x"), &Overrides::default()).unwrap(); assert_eq!(resolved[0].versions, VersionSpec::Latest); } + + #[test] + fn default_framing_section_reaches_a_declared_target() { + let cfg = parse("[generate]\ndefault_framing = \"jsonrpc\"\n"); + let declared = [DeclaredLang { + language: "rust".into(), + lang_version: "1.75".into(), + }]; + let resolved = resolve(&cfg, &declared, Path::new("/x"), &Overrides::default()).unwrap(); + assert_eq!(resolved[0].default_framing.as_deref(), Some("jsonrpc")); + } + + #[test] + fn target_default_framing_wins_over_section() { + let cfg = parse( + "[generate]\ndefault_framing = \"jsonrpc\"\n\n\ + [[generate.target]]\nlanguage = \"rust\"\nlang_version = \"1.70.0\"\n\ + default_framing = \"datagram\"\n", + ); + let declared = []; + let resolved = resolve(&cfg, &declared, Path::new("/x"), &Overrides::default()).unwrap(); + assert_eq!(resolved[0].default_framing.as_deref(), Some("datagram")); + } + + #[test] + fn default_framing_absent_is_none() { + let cfg = parse("[generate]\n"); + let declared = [DeclaredLang { + language: "rust".into(), + lang_version: "1.75".into(), + }]; + let resolved = resolve(&cfg, &declared, Path::new("/x"), &Overrides::default()).unwrap(); + assert_eq!(resolved[0].default_framing, None); + } } diff --git a/tests/cli/generate.rs b/tests/cli/generate.rs index 2fa0667..d8aaff1 100644 --- a/tests/cli/generate.rs +++ b/tests/cli/generate.rs @@ -178,6 +178,46 @@ fn rejects_an_unconfigured_target() { // -------- lib mode -------- +#[test] +fn comline_toml_default_framing_reaches_the_rust_generator() { + let temp = tempfile::tempdir().unwrap(); + let project = copy_fixture("chat_project", temp.path()); + fs::write( + project.join("comline.toml"), + "[generate]\ndefault_framing = \"jsonrpc\"\n", + ) + .unwrap(); + + comline_cmd() + .current_dir(&project) + .args(["generate", "--target", "rust"]) + .assert() + .success(); + + // `protocol Chat` has no `@framing` of its own, so it picks up the + // package default: the JSON-RPC stack, not the datagram one. + let chat = fs::read_to_string(project.join("generated/rust/chat.rs")).unwrap(); + assert!(chat.contains("comline_runtime::framing::JsonRpcFraming")); + assert!(chat.contains("Client::connect_with_framing")); + assert!(!chat.contains("FRAMING_DATAGRAM")); +} + +#[test] +fn without_comline_toml_a_protocol_stays_on_datagram_framing() { + let temp = tempfile::tempdir().unwrap(); + let project = copy_fixture("chat_project", temp.path()); + + comline_cmd() + .current_dir(&project) + .args(["generate", "--target", "rust"]) + .assert() + .success(); + + let chat = fs::read_to_string(project.join("generated/rust/chat.rs")).unwrap(); + assert!(chat.contains("FRAMING_DATAGRAM")); + assert!(!chat.contains("JsonRpcFraming")); +} + #[test] fn lib_mode_emits_a_crate() { let temp = tempfile::tempdir().unwrap();