From 9f878ddd13d61bca3c588318841021451cef00b7 Mon Sep 17 00:00:00 2001 From: Kinflou Date: Sat, 31 Oct 2026 22:19:37 +0800 Subject: [PATCH 1/3] generate: map encodings in comline.toml and fill the request's [generate] and [[generate.target]] take encodings = { "" = "" | { type, package, version } }, the target's over the section's by canonical name. generate gathers the encodings the package and its dependencies declare, with the official ones, and fills GenerateRequest.encodings through comline_codegen::encodings::fill: comline.toml's mapping, else the declaring package's suggestion, else an error naming the encoding, the language and the comline.toml syntax, for an encoding the package uses. Pins generation b116831, comline-rust 650c684 and comline-typescript 765fc2d (the encodings-mapping branches), the fixture generators too. Generated output for chat_project, simple_project and validated_project is byte-identical to main's, Rust and TypeScript, code and lib. --- Cargo.lock | 6 +- Cargo.toml | 6 +- src/commands/generate.rs | 30 ++++- src/commands/new.rs | 1 + src/gen_config.rs | 88 +++++++++++++++ tests/cli/generate.rs | 160 +++++++++++++++++++++++++++ tests/fixtures/generators/Cargo.lock | 4 +- tests/fixtures/generators/Cargo.toml | 4 +- 8 files changed, 286 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3725738..b39bdb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,7 @@ dependencies = [ [[package]] name = "comline-codegen" version = "0.1.0" -source = "git+https://github.com/ComlineProject/generation?rev=2d922cebb347ea1939d90c713ccf77a7ec82db65#2d922cebb347ea1939d90c713ccf77a7ec82db65" +source = "git+https://github.com/ComlineProject/generation?rev=b116831182c32e70fb4452ff9fce4b463ce4bd99#b116831182c32e70fb4452ff9fce4b463ce4bd99" dependencies = [ "comline-core", "eyre", @@ -492,7 +492,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=8bbce6dbe7f872550f4300025e5c139ff5ec304c#8bbce6dbe7f872550f4300025e5c139ff5ec304c" +source = "git+https://github.com/ComlineProject/comline-rust?rev=650c684770b0fa96f60e9fc9e04ad91413c48c68#650c684770b0fa96f60e9fc9e04ad91413c48c68" dependencies = [ "comline-codegen", "comline-core", @@ -502,7 +502,7 @@ dependencies = [ [[package]] name = "comline-codegen-typescript" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-typescript?rev=be8bc79d99f3196cebfded93f08c26e0de1c72fc#be8bc79d99f3196cebfded93f08c26e0de1c72fc" +source = "git+https://github.com/ComlineProject/comline-typescript?rev=765fc2d1177d0044498b00160000ac5084540af2#765fc2d1177d0044498b00160000ac5084540af2" dependencies = [ "comline-codegen", "comline-core", diff --git a/Cargo.toml b/Cargo.toml index 7e6b0ea..7a63335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,9 +38,9 @@ comline-core = { git = "https://github.com/ComlineProject/core", rev = "98d64fae # `comline-codegen`'s types, so the rev below must be byte-identical to the # one *they* pin - Cargo only unifies a git dependency across identical rev # strings, and two copies of `GenRequest` are two incompatible types. -comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "2d922cebb347ea1939d90c713ccf77a7ec82db65" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "8bbce6dbe7f872550f4300025e5c139ff5ec304c", optional = true } -comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "be8bc79d99f3196cebfded93f08c26e0de1c72fc", optional = true } +comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "b116831182c32e70fb4452ff9fce4b463ce4bd99" } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "650c684770b0fa96f60e9fc9e04ad91413c48c68", optional = true } +comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "765fc2d1177d0044498b00160000ac5084540af2", optional = true } # Runs a `generator = ".wasm"` target (see src/external.rs): no # preopened directories, no environment, no network. Exact pins, bumped # together. diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 6180d70..daf7da1 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -108,6 +108,21 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo }) .unwrap_or_default(); + // The encodings the package and its dependencies declare, each with its + // suggested mappings: what a target's `encodings` in comline.toml + // override, beside the official ones every package sees. + let declared_encodings: Vec = config_units + .iter() + .chain( + context + .dependency_configs + .iter() + .flat_map(|(_, units)| units), + ) + .filter(|u| matches!(u, ConfigUnit::Encoding { .. })) + .cloned() + .collect(); + // Crate / package name for a `lib`-mode manifest — the congregation name, // `::` flattened to `-` so it is a valid crate name. let package_name = context.config.name.value.replace("::", "-"); @@ -260,7 +275,7 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo }); for gv in &versions { - let request = GenerateRequest { + let mut request = GenerateRequest { mode, schemas: gv.schemas.clone(), package: PackageMeta { @@ -281,10 +296,19 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo external_std: t.external_std, async_api: t.async_api, validate_on_decode: t.validate_on_decode, - // Filled from the encodings the package declares once - // `comline.toml` maps them (core#120, B3). + // Filled just below. encodings: Default::default(), }; + // The type each encoding the package uses maps to in this + // language: comline.toml's mapping, else the declaring package's + // suggestion. One left with neither is refused here, by name. + comline_codegen::encodings::fill( + &mut request, + comline_core::encodings::official::units(), + &declared_encodings, + &t.encodings, + ) + .map_err(|e| miette!("target `{}`: {e}", t.language))?; // Deliberately not `core_err`: a generator's errors are about // schema/package *structure* (e.g. a namespace that's both a // schema and a parent of deeper schemas), not source *text* - diff --git a/src/commands/new.rs b/src/commands/new.rs index 4406f0b..e45b9f2 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -47,6 +47,7 @@ const COMLINE_TOML: &str = "\ # package_versions = \"latest\" # latest | all | [\"0.3.0\", \"0.4.0\"] # validate_on_decode = \"off\" # run `@validators` while decoding: off | collect | first # async = false # protocols on a connection get async handlers and senders +# encodings = { \"com.acme.lines@1\" = \"acme::LinesFraming\" } # the type implementing an encoding, over its package's suggestion # # One block per declared language you want to configure. A block sets that # language's output; it does not decide which languages are generated, and diff --git a/src/gen_config.rs b/src/gen_config.rs index 655f8c1..39f7935 100644 --- a/src/gen_config.rs +++ b/src/gen_config.rs @@ -120,11 +120,72 @@ pub struct Generate { /// A generator whose output is async anyway ignores it. Default `false`. #[serde(rename = "async")] pub async_api: Option, + /// Which type implements each encoding (format, framing, handshake) in + /// the generated code, by encoding name: + /// `encodings = { "com.acme.lines@1" = "acme::LinesFraming" }`, or with + /// the package it comes from, `{ type = "...", package = "...", version + /// = "..." }`. Overrides the declaring package's suggested mapping, and + /// maps an encoding that has none. A target's own `encodings` override + /// these, name by name. + pub encodings: EncodingMap, /// `[[generate.target]]` blocks. #[serde(rename = "target")] pub targets: Vec, } +/// `encodings = { ... }`, by encoding name as written. +pub type EncodingMap = std::collections::BTreeMap; + +/// One entry of `encodings`: the type alone, or with its package. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(untagged)] +pub enum EncodingEntry { + /// `"acme::LinesFraming"`. + Type(String), + /// `{ type = "LinesFraming", package = "acme-encodings", version = "^0.2" }`. + Full(EncodingTable), +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EncodingTable { + #[serde(rename = "type")] + pub ty: String, + pub package: Option, + pub version: Option, +} + +impl EncodingEntry { + pub fn mapping(&self) -> comline_codegen::protocol::EncodingMapping { + match self { + EncodingEntry::Type(ty) => comline_codegen::protocol::EncodingMapping { + type_path: ty.clone(), + package: None, + version: None, + }, + EncodingEntry::Full(t) => comline_codegen::protocol::EncodingMapping { + type_path: t.ty.clone(), + package: t.package.clone(), + version: t.version.clone(), + }, + } + } +} + +/// `[generate]`'s `encodings` with a target's over them, by canonical +/// name, so `jsonrpc@1` in one and `json-rpc` in the other are the same +/// entry and the target's wins. +fn merge_encodings( + section: &EncodingMap, + target: &EncodingMap, +) -> std::collections::BTreeMap { + let mut merged = std::collections::BTreeMap::new(); + for (name, entry) in section.iter().chain(target) { + merged.insert(comline_codegen::encodings::canonical(name), entry.mapping()); + } + merged +} + /// One `[[generate.target]]` block. #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] @@ -155,6 +216,9 @@ pub struct Target { pub validate_on_decode: Option, #[serde(rename = "async")] pub async_api: Option, + /// See [`Generate::encodings`]; these override those, name by name. + #[serde(default)] + pub encodings: EncodingMap, /// `lib` mode only: write the crate straight into `out` instead of /// `//`. Target-only (no `[generate]` default, no flag/env) /// — a shared `out` across several `lib` targets needs the per-language @@ -240,6 +304,9 @@ pub struct ResolvedTarget { pub validate_on_decode: ValidateOnDecode, /// See [`Generate::async_api`]. pub async_api: bool, + /// [`Generate::encodings`] with the target's over them, by canonical + /// encoding name. + pub encodings: std::collections::BTreeMap, } /// Variables a `layout` template can reference. @@ -393,6 +460,7 @@ pub fn resolve( external_std: None, validate_on_decode: None, async_api: None, + encodings: Default::default(), flatten: false, }] } else { @@ -580,6 +648,7 @@ pub fn resolve( external_std, validate_on_decode, async_api: t.async_api.or(cfg.generate.async_api).unwrap_or(false), + encodings: merge_encodings(&cfg.generate.encodings, &t.encodings), }); } @@ -1022,4 +1091,23 @@ mod tests { "`validate_on_decode = \"always\"` for target `rust` - expected one of `off`, `collect`, `first`" ); } + + #[test] + fn a_targets_encodings_override_the_sections_by_canonical_name() { + let cfg: ComlineToml = toml::from_str( + "[generate]\n\ + encodings = { \"jsonrpc@1\" = \"section::Rpc\", \"com.acme.cbor@1\" = \"section::Cbor\" }\n\ + [[generate.target]]\n\ + language = \"rust\"\n\ + encodings = { \"json-rpc\" = { type = \"mine::Rpc\", package = \"mine\", version = \"1\" } }\n", + ) + .unwrap(); + let merged = merge_encodings(&cfg.generate.encodings, &cfg.generate.targets[0].encodings); + let keys: Vec<&str> = merged.keys().map(String::as_str).collect(); + assert_eq!(keys, ["com.acme.cbor@1", "jsonrpc"]); + assert_eq!(merged["jsonrpc"].type_path, "mine::Rpc"); + assert_eq!(merged["jsonrpc"].package.as_deref(), Some("mine")); + assert_eq!(merged["com.acme.cbor@1"].type_path, "section::Cbor"); + assert_eq!(merged["com.acme.cbor@1"].package, None); + } } diff --git a/tests/cli/generate.rs b/tests/cli/generate.rs index d5f41c2..0d32681 100644 --- a/tests/cli/generate.rs +++ b/tests/cli/generate.rs @@ -913,3 +913,163 @@ fn async_without_a_connection_protocol_warns() { .success() .stderr(predicate::str::contains(warning).not()); } + +/// The profile `acme` ships for its framing. +const LINES_PROFILE: &str = r#"{ + "name": "com.acme.lines@1", + "kind": "framing", + "facts": { + "calls": { "value": "by_name", "proven_by": ["lines/request"] }, + "errors": { "value": "by_name", "proven_by": ["lines/error"] } + } +}"#; + +/// `acme`, declaring the framing `com.acme.lines@1` with `map` as its +/// suggested mappings, and `app`, depending on it with a protocol over that +/// framing. Returns `app`'s directory. +fn acme_and_app(root: &std::path::Path, map: &str) -> std::path::PathBuf { + let acme = root.join("acme"); + fs::create_dir_all(acme.join("src")).unwrap(); + fs::write( + acme.join("config.idp"), + format!( + "congregation acme\nspecification_version = 1\n\n\ + encodings = {{\n \"com.acme.lines@1\" = {{\n kind = \"framing\"\n \ + profile = \"lines.json\"\n map = {{ {map} }}\n }}\n}}\n" + ), + ) + .unwrap(); + fs::write(acme.join("lines.json"), LINES_PROFILE).unwrap(); + fs::write( + acme.join("src/models.ids"), + "struct Line {\n text: string\n}\n", + ) + .unwrap(); + + let app = root.join("app"); + fs::create_dir_all(app.join("src")).unwrap(); + fs::write( + app.join("config.idp"), + "congregation app\nspecification_version = 1\n\n\ + code_generation = {\n languages = {\n rust#1.85.0 = {}\n }\n}\n\n\ + settings = { annotations = { framing = { allowed = true } } }\n\n\ + dependencies = {\n acme = { path = \"../acme\" }\n}\n", + ) + .unwrap(); + fs::write( + app.join("src/api.ids"), + "@framing = \"com.acme.lines@1\"\nprotocol Echo {\n function echo(text: string) -> string;\n}\n", + ) + .unwrap(); + app +} + +fn generated_api(app: &std::path::Path) -> String { + fs::read_to_string(app.join("generated/rust/api.rs")).unwrap() +} + +/// A dependency's framing: the declaring package's suggested mapping when +/// comline.toml says nothing, and comline.toml's (the target's over +/// `[generate]`'s) when it does. +#[test] +fn a_dependencys_framing_generates_its_mapped_type() { + let temp = tempfile::tempdir().unwrap(); + let app = acme_and_app( + temp.path(), + "rust = { type = \"acme::LinesFraming\" package = \"acme\" }", + ); + let generate = || { + comline_cmd() + .current_dir(&app) + .args(["generate", "--target", "rust"]) + .assert() + }; + + generate().success(); + assert!( + generated_api(&app).contains("let framing = acme::LinesFraming;"), + "{}", + generated_api(&app) + ); + + fs::write( + app.join("comline.toml"), + "[generate]\nencodings = { \"com.acme.lines@1\" = \"section::Lines\" }\n", + ) + .unwrap(); + generate().success(); + assert!(generated_api(&app).contains("let framing = section::Lines;")); + + fs::write( + app.join("comline.toml"), + "[generate]\nencodings = { \"com.acme.lines@1\" = \"section::Lines\" }\n\n\ + [[generate.target]]\nlanguage = \"rust\"\n\ + encodings = { \"com.acme.lines@1\" = { type = \"mine::Lines\", package = \"mine\", version = \"1\" } }\n", + ) + .unwrap(); + generate().success(); + let api = generated_api(&app); + assert!(api.contains("let framing = mine::Lines;"), "{api}"); + assert!(!api.contains("section::Lines"), "{api}"); +} + +#[test] +fn an_unmapped_dependency_framing_is_an_error_naming_it() { + let temp = tempfile::tempdir().unwrap(); + // A suggestion for TypeScript only: nothing for Rust. + let app = acme_and_app(temp.path(), "typescript = { type = \"LinesFraming\" }"); + comline_cmd() + .current_dir(&app) + .args(["generate", "--target", "rust"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "encoding `com.acme.lines@1` has no rust mapping", + )) + .stderr(predicate::str::contains( + "encodings = { \"com.acme.lines@1\" = \"\" }", + )); + assert!(!app.join("generated").exists(), "nothing was written"); +} + +#[test] +fn a_bad_comline_toml_encoding_entry_is_refused() { + let temp = tempfile::tempdir().unwrap(); + let app = acme_and_app(temp.path(), "rust = { type = \"acme::LinesFraming\" }"); + fs::write( + app.join("comline.toml"), + "[generate]\nencodings = { \"com.acme.lines@1\" = { ty = \"x::Lines\" } }\n", + ) + .unwrap(); + comline_cmd() + .current_dir(&app) + .args(["generate", "--target", "rust"]) + .assert() + .failure(); +} + +/// cli#81: a framing nothing declares used to generate as the datagram +/// framing. It's an error, before anything is generated. +#[test] +fn an_unknown_framing_is_an_error() { + let temp = tempfile::tempdir().unwrap(); + let project = fixture_project(temp.path()); + let config = fs::read_to_string(project.join("config.idp")).unwrap(); + fs::write( + project.join("config.idp"), + format!("{config}\nsettings = {{ annotations = {{ framing = {{ allowed = true }} }} }}\n"), + ) + .unwrap(); + fs::write( + project.join("src/pigeon.ids"), + "@framing = \"com.acme.pigeon@1\"\nprotocol Coo {\n function coo();\n}\n", + ) + .unwrap(); + comline_cmd() + .current_dir(&project) + .args(["generate", "--target", "rust"]) + .assert() + .failure() + .stderr(predicate::str::contains("com.acme.pigeon@1")); + assert!(!project.join("generated").exists()); +} diff --git a/tests/fixtures/generators/Cargo.lock b/tests/fixtures/generators/Cargo.lock index c2da2e9..bd2729a 100644 --- a/tests/fixtures/generators/Cargo.lock +++ b/tests/fixtures/generators/Cargo.lock @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "comline-codegen" version = "0.1.0" -source = "git+https://github.com/ComlineProject/generation?rev=2d922cebb347ea1939d90c713ccf77a7ec82db65#2d922cebb347ea1939d90c713ccf77a7ec82db65" +source = "git+https://github.com/ComlineProject/generation?rev=b116831182c32e70fb4452ff9fce4b463ce4bd99#b116831182c32e70fb4452ff9fce4b463ce4bd99" dependencies = [ "comline-core", "eyre", @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=8bbce6dbe7f872550f4300025e5c139ff5ec304c#8bbce6dbe7f872550f4300025e5c139ff5ec304c" +source = "git+https://github.com/ComlineProject/comline-rust?rev=650c684770b0fa96f60e9fc9e04ad91413c48c68#650c684770b0fa96f60e9fc9e04ad91413c48c68" dependencies = [ "comline-codegen", "comline-core", diff --git a/tests/fixtures/generators/Cargo.toml b/tests/fixtures/generators/Cargo.toml index 2bbc4d3..272507f 100644 --- a/tests/fixtures/generators/Cargo.toml +++ b/tests/fixtures/generators/Cargo.toml @@ -12,5 +12,5 @@ publish = false [dependencies] # The same revs the CLI pins, so the real generator's output is comparable # byte for byte with the CLI's built-in one. -comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "2d922cebb347ea1939d90c713ccf77a7ec82db65" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "8bbce6dbe7f872550f4300025e5c139ff5ec304c" } +comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "b116831182c32e70fb4452ff9fce4b463ce4bd99" } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "650c684770b0fa96f60e9fc9e04ad91413c48c68" } From 2b48d2b9138f8cc27a95d3005235cb2284f10c8a Mon Sep 17 00:00:00 2001 From: Kinflou Date: Sat, 31 Oct 2026 22:33:48 +0800 Subject: [PATCH 2/3] generate: a built-in's support-table refusal comes before the unmapped-encoding error; pin comline-rust 0e5c60e --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/commands/generate.rs | 20 ++++++++++++++++--- tests/cli/generate.rs | 29 ++++++++++++++++++++++++++++ tests/fixtures/generators/Cargo.lock | 2 +- tests/fixtures/generators/Cargo.toml | 2 +- 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b39bdb6..d52bdab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,7 +492,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=650c684770b0fa96f60e9fc9e04ad91413c48c68#650c684770b0fa96f60e9fc9e04ad91413c48c68" +source = "git+https://github.com/ComlineProject/comline-rust?rev=0e5c60ebdfab9cd172f23691a18084d651bfb33a#0e5c60ebdfab9cd172f23691a18084d651bfb33a" dependencies = [ "comline-codegen", "comline-core", diff --git a/Cargo.toml b/Cargo.toml index 7a63335..01eb205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ comline-core = { git = "https://github.com/ComlineProject/core", rev = "98d64fae # one *they* pin - Cargo only unifies a git dependency across identical rev # strings, and two copies of `GenRequest` are two incompatible types. comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "b116831182c32e70fb4452ff9fce4b463ce4bd99" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "650c684770b0fa96f60e9fc9e04ad91413c48c68", optional = true } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "0e5c60ebdfab9cd172f23691a18084d651bfb33a", optional = true } comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "765fc2d1177d0044498b00160000ac5084540af2", optional = true } # Runs a `generator = ".wasm"` target (see src/external.rs): no # preopened directories, no environment, no network. Exact pins, bumped diff --git a/src/commands/generate.rs b/src/commands/generate.rs index daf7da1..1012c4d 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -302,13 +302,27 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo // The type each encoding the package uses maps to in this // language: comline.toml's mapping, else the declaring package's // suggestion. One left with neither is refused here, by name. - comline_codegen::encodings::fill( + if let Err(e) = comline_codegen::encodings::fill( &mut request, comline_core::encodings::official::units(), &declared_encodings, &t.encodings, - ) - .map_err(|e| miette!("target `{}`: {e}", t.language))?; + ) { + // A built-in generator's support table is what says why it + // can't take this package (TypeScript has no MessagePack + // codec, and a tracking link for it); an encoding it has no + // mapping for is that same fact, so the table's refusal comes + // first, worded as the generator's own. + if let Source::Builtin(resolved) = &source { + let options = features::Options::of(&request); + if let Some(refusal) = + lang_targets::unsupported(&t.language, resolved, &request.schemas, &options) + { + return Err(miette!("`{}` generator: {refusal}", t.language)); + } + } + return Err(miette!("target `{}`: {e}", t.language)); + } // Deliberately not `core_err`: a generator's errors are about // schema/package *structure* (e.g. a namespace that's both a // schema and a parent of deeper schemas), not source *text* - diff --git a/tests/cli/generate.rs b/tests/cli/generate.rs index 0d32681..d1462f6 100644 --- a/tests/cli/generate.rs +++ b/tests/cli/generate.rs @@ -1073,3 +1073,32 @@ fn an_unknown_framing_is_an_error() { .stderr(predicate::str::contains("com.acme.pigeon@1")); assert!(!project.join("generated").exists()); } + +/// A format a built-in generator doesn't support is refused with its support +/// table's message and tracking link, not the encoding resolution's "no +/// typescript mapping" (which is true, but not the reason or the way out). +#[test] +fn an_unsupported_format_gives_the_support_tables_message() { + let temp = tempfile::tempdir().unwrap(); + let project = copy_fixture("chat_project", temp.path()); + let config = project.join("config.idp"); + let idp = fs::read_to_string(&config).unwrap(); + fs::write( + &config, + idp.replace("rust#1.85.0 = {}", "rust#1.85.0 = {}\n typescript#5.0 = {}"), + ) + .unwrap(); + add_to_config(&project, "wire = { format = \"msgpack@1\" }"); + + comline_cmd() + .current_dir(&project) + .args(["generate", "--target", "typescript"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "the typescript generator doesn't support the `msgpack@1` format (format-msgpack): \ + `@comline/runtime` has no MessagePack codec yet", + )) + .stderr(predicate::str::contains("no typescript mapping").not()); + assert!(!project.join("generated").exists(), "nothing was written"); +} diff --git a/tests/fixtures/generators/Cargo.lock b/tests/fixtures/generators/Cargo.lock index bd2729a..580b467 100644 --- a/tests/fixtures/generators/Cargo.lock +++ b/tests/fixtures/generators/Cargo.lock @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=650c684770b0fa96f60e9fc9e04ad91413c48c68#650c684770b0fa96f60e9fc9e04ad91413c48c68" +source = "git+https://github.com/ComlineProject/comline-rust?rev=0e5c60ebdfab9cd172f23691a18084d651bfb33a#0e5c60ebdfab9cd172f23691a18084d651bfb33a" dependencies = [ "comline-codegen", "comline-core", diff --git a/tests/fixtures/generators/Cargo.toml b/tests/fixtures/generators/Cargo.toml index 272507f..cbb2da1 100644 --- a/tests/fixtures/generators/Cargo.toml +++ b/tests/fixtures/generators/Cargo.toml @@ -13,4 +13,4 @@ publish = false # The same revs the CLI pins, so the real generator's output is comparable # byte for byte with the CLI's built-in one. comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "b116831182c32e70fb4452ff9fce4b463ce4bd99" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "650c684770b0fa96f60e9fc9e04ad91413c48c68" } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "0e5c60ebdfab9cd172f23691a18084d651bfb33a" } From 5eb9052fe8ab51ca8050d734c690983b3cd91729 Mon Sep 17 00:00:00 2001 From: Kinflou Date: Sat, 31 Oct 2026 22:38:21 +0800 Subject: [PATCH 3/3] tests: rustfmt --- tests/cli/generate.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/cli/generate.rs b/tests/cli/generate.rs index d1462f6..1a5134d 100644 --- a/tests/cli/generate.rs +++ b/tests/cli/generate.rs @@ -1085,7 +1085,10 @@ fn an_unsupported_format_gives_the_support_tables_message() { let idp = fs::read_to_string(&config).unwrap(); fs::write( &config, - idp.replace("rust#1.85.0 = {}", "rust#1.85.0 = {}\n typescript#5.0 = {}"), + idp.replace( + "rust#1.85.0 = {}", + "rust#1.85.0 = {}\n typescript#5.0 = {}", + ), ) .unwrap(); add_to_config(&project, "wire = { format = \"msgpack@1\" }");