From 3715c3bced70d4e1bd2d8147b96841bd55a85bea Mon Sep 17 00:00:00 2001 From: Kinflou Date: Sun, 1 Nov 2026 06:28:53 +0800 Subject: [PATCH 1/2] inspect: each protocol's choices, their sources, and its guarantees `comline inspect` compiles the package without writing to .comline/, like check, and prints per protocol the format, framing and handshake, their parameters and where each came from, then a row per guarantee: held or given up, why, and whether it's accepted. --json prints the same. The model is core's (comline_core::inspect, core#150). Pins: core 4706658, generation 0601f00, comline-rust 6e8715a, comline-typescript 470dc5b. --- Cargo.lock | 10 +- Cargo.toml | 8 +- docs/cli.md | 57 +++++++++ src/cli.rs | 23 ++++ src/commands/inspect.rs | 121 +++++++++++++++++++ src/commands/mod.rs | 1 + src/main.rs | 1 + tests/cli/inspect.rs | 166 +++++++++++++++++++++++++++ tests/cli/main.rs | 1 + tests/fixtures/generators/Cargo.lock | 8 +- tests/fixtures/generators/Cargo.toml | 4 +- 11 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 src/commands/inspect.rs create mode 100644 tests/cli/inspect.rs diff --git a/Cargo.lock b/Cargo.lock index b1fecba..ebb6c6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,7 +562,7 @@ dependencies = [ [[package]] name = "comline-codegen" version = "0.1.0" -source = "git+https://github.com/ComlineProject/generation?rev=de7ceb4c266d1fb5d6dc3c918d84cb226003f6b6#de7ceb4c266d1fb5d6dc3c918d84cb226003f6b6" +source = "git+https://github.com/ComlineProject/generation?rev=0601f004302ebb93e6804732419ccf459902e84a#0601f004302ebb93e6804732419ccf459902e84a" dependencies = [ "comline-core", "eyre", @@ -576,7 +576,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=5346678b82fc275fca741bf5ca0cfad1e8edf075#5346678b82fc275fca741bf5ca0cfad1e8edf075" +source = "git+https://github.com/ComlineProject/comline-rust?rev=6e8715ac126ab78316dd6a2712165d716351fab1#6e8715ac126ab78316dd6a2712165d716351fab1" dependencies = [ "comline-codegen", "comline-core", @@ -586,7 +586,7 @@ dependencies = [ [[package]] name = "comline-codegen-typescript" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-typescript?rev=c2096cd500f7cd3a83d495fbbbf5df72bc12ecc7#c2096cd500f7cd3a83d495fbbbf5df72bc12ecc7" +source = "git+https://github.com/ComlineProject/comline-typescript?rev=470dc5bad2f7e36e1cc2f44e3e57fde864a42315#470dc5bad2f7e36e1cc2f44e3e57fde864a42315" dependencies = [ "comline-codegen", "comline-core", @@ -596,7 +596,7 @@ dependencies = [ [[package]] name = "comline-core" version = "0.1.0" -source = "git+https://github.com/ComlineProject/core?rev=2ae998634bad418e4a03ce7494e673275dafcc01#2ae998634bad418e4a03ce7494e673275dafcc01" +source = "git+https://github.com/ComlineProject/core?rev=47066583002dbca27975827106f9e34761d0dc64#47066583002dbca27975827106f9e34761d0dc64" dependencies = [ "ariadne", "blake3", @@ -624,7 +624,7 @@ dependencies = [ [[package]] name = "comline-core-stdlib" version = "0.1.0" -source = "git+https://github.com/ComlineProject/core?rev=2ae998634bad418e4a03ce7494e673275dafcc01#2ae998634bad418e4a03ce7494e673275dafcc01" +source = "git+https://github.com/ComlineProject/core?rev=47066583002dbca27975827106f9e34761d0dc64#47066583002dbca27975827106f9e34761d0dc64" [[package]] name = "comline-runtime" diff --git a/Cargo.toml b/Cargo.toml index 4a0cf10..c6214f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,15 +32,15 @@ eyre = "0.6.8" # needs real I/O (git fetch, filesystem), so it's off by default for # `comline-core`'s wasm32 consumers (the playground editor, the language # server); the CLI is the one consumer that actually runs `comline build`. -comline-core = { git = "https://github.com/ComlineProject/core", rev = "2ae998634bad418e4a03ce7494e673275dafcc01", features = ["deps"] } +comline-core = { git = "https://github.com/ComlineProject/core", rev = "47066583002dbca27975827106f9e34761d0dc64", features = ["deps"] } # All three pinned to revs that agree on one `comline-codegen`, and bumped # together: comline-rust and comline-typescript's codegen crates consume # `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 = "de7ceb4c266d1fb5d6dc3c918d84cb226003f6b6" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "5346678b82fc275fca741bf5ca0cfad1e8edf075", optional = true } -comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "c2096cd500f7cd3a83d495fbbbf5df72bc12ecc7", optional = true } +comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "0601f004302ebb93e6804732419ccf459902e84a" } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "6e8715ac126ab78316dd6a2712165d716351fab1", optional = true } +comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "470dc5bad2f7e36e1cc2f44e3e57fde864a42315", optional = true } # `comline conformance`: the conformance runner (corpus loading, judging, # profile proofs) and its case format, a public spec. Pinned by git rev. comline-runtime-conformance = { git = "https://github.com/ComlineProject/runtime", rev = "6f8f73a247f0aa4ac8b7041b2fdc4b02bce0e0d6" } diff --git a/docs/cli.md b/docs/cli.md index 5a48bc2..ace90a1 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -16,6 +16,7 @@ This is the long-form reference. For a two-minute tour see the - [`new`](#comline-new) - [`add`](#comline-add) - [`check`](#comline-check) + - [`inspect`](#comline-inspect) - [`build`](#comline-build) - [`generate`](#comline-generate) - [`diff`](#comline-diff) @@ -195,6 +196,62 @@ comline --path ./services/users check Exit `0` if valid, `1` if a schema or the config is invalid, `2` if the directory is not a Comline project. +### `comline inspect` + +``` +comline inspect [--json] +``` + +What each protocol speaks on the wire, and which guarantees it keeps. Like +`check`, it compiles the package and writes nothing to `.comline/`. + +For each protocol, a short header names the format, framing and handshake it +resolves to, with their parameters and where each came from: + +| Source | Meaning | +|---|---| +| `from @framing` / `from @handshake` | the protocol's own annotation | +| `from config.idp ...` | the package's `wire.format`, `wire.framing` or `handshake` | +| `core's default` | nothing named one: `packed@1`, `datagram@2`, `comline@1` | +| `JSON-RPC's format` | the framing is JSON-RPC and the package names no format | + +Then one row per guarantee (schema agreement, encoding agreement, encoding +conformance, version honesty), held or given up. A given-up row says why, and +whether it has been accepted. A dependency's protocol is accepted by this +package's `accept_unverified`. Who accepts this package's own protocols isn't +known here: each consumer does that in its own `config.idp`. A protocol's +`@unverified` reason is in its header. + +``` +$ comline --plain inspect +protocol api::Legacy (shop) + format json@1 JSON-RPC's format + framing jsonrpc (method_names = qualified) from @framing + handshake none from @handshake + unverified "the 2009 gateway can't run a handshake" + + schema agreement given up + because handshake `none` + acceptance: up to each consumer's config.idp + encoding agreement given up + because handshake `none` + acceptance: up to each consumer's config.idp + encoding conformance held + version honesty held +``` + +The report goes to stdout. `--json` prints the same as JSON, as +`{ "protocols": [...] }`, with each choice's `source` given as `annotation`, +`package_default`, `core_default` or `framing`. Each guarantee's `acceptance` +is `{ "state": "not_needed" | "unknown" | "not_accepted" }`, or +`{ "state": "accepted", "by": "" }`. + +A protocol's compatibility facts (its line, version and shape hash) aren't +shown, because they aren't built yet (core#119). + +Exit `0` on success, `1` if a schema or the config is invalid, `2` if the +directory is not a Comline project. + ### `comline build` ``` diff --git a/src/cli.rs b/src/cli.rs index 126329b..be085a9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -77,6 +77,29 @@ first error found. Unlike `build`, this never writes to `.comline/` and never \ bumps the version, so it is safe to run in editors and pre-commit hooks.")] Check, + /// Show what each protocol speaks on the wire and which guarantees it keeps + #[command(long_about = "\ +For each protocol, the format, framing and handshake it resolves to, their \ +parameters, and where each came from: the protocol's own `@framing` or \ +`@handshake`, the package's `wire` or `handshake` in `config.idp`, or core's \ +default. Then every guarantee (schema agreement, encoding agreement, encoding \ +conformance, version honesty), held or given up, with the cause, the \ +`@unverified` reason, and whether it has been accepted. A dependency's \ +protocol is accepted by this package's `accept_unverified`; who accepts this \ +package's own protocols isn't known here. + +Like `check`, this never writes to `.comline/`. `--json` prints the same as \ +JSON. + +Examples: + comline inspect + comline inspect --json")] + Inspect { + /// Print the report as JSON + #[arg(long)] + json: bool, + }, + /// Validate the project and write generated code for each configured target #[command(long_about = "\ Validate the project (no version bump, no `.comline/` write), then write \ diff --git a/src/commands/inspect.rs b/src/commands/inspect.rs new file mode 100644 index 0000000..a0c051e --- /dev/null +++ b/src/commands/inspect.rs @@ -0,0 +1,121 @@ +//! `comline inspect` — what each protocol resolves to on the wire, where +//! each choice came from, and which guarantees it keeps. Read-only, like +//! `check`: nothing is written to `.comline/`. +//! +//! The model is core's (`comline_core::inspect`), shared with the language +//! server's hover. The report goes to stdout; `--json` prints the same as +//! JSON. + +use std::io::Write as _; +use std::path::Path; + +use anstyle::{AnsiColor, Color, Style}; +use comline_core::inspect::{self, Acceptance, Choice, ProtocolSummary, Source}; +use miette::{IntoDiagnostic, Result}; + +use crate::commands::{core_err, ensure_project}; +use crate::ui; + +const HELD: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green))); +const GIVEN_UP: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))); +const DIM: Style = Style::new().dimmed(); +const BOLD: Style = Style::new().bold(); + +pub fn run(work_dir: &Path, json: bool) -> Result<()> { + ensure_project(work_dir)?; + + let spinner = ui::spinner("compiling schemas"); + let outcome = comline_core::package::build::compile_package(work_dir); + ui::finish_spinner(spinner); + let context = outcome.map_err(|e| core_err("validation failed", e))?; + + let protocols = inspect::protocols(&context); + let mut out = anstream::stdout().lock(); + if json { + let report = serde_json::json!({ "protocols": protocols }); + writeln!(out, "{}", serde_json::to_string_pretty(&report).into_diagnostic()?) + .into_diagnostic()?; + return Ok(()); + } + + if protocols.is_empty() { + ui::note("No protocols to inspect"); + return Ok(()); + } + let text = protocols.iter().map(|p| render(p, ui::plain())).collect::>().join("\n"); + write!(out, "{text}").into_diagnostic()?; + Ok(()) +} + +/// One protocol: a header of choices, then a row per guarantee. Fits 80 +/// columns: anything long goes on its own indented line. +pub fn render(p: &ProtocolSummary, plain: bool) -> String { + let style = |s: Style, text: &str| if plain { text.to_string() } else { format!("{s}{text}{s:#}") }; + let mut out = String::new(); + + let whose = if p.dependency { format!("dependency {}", p.package) } else { p.package.clone() }; + out += &format!( + "{} {}\n", + style(BOLD, &format!("protocol {}::{}", p.schema, p.name)), + style(DIM, &format!("({whose})")) + ); + for (key, choice) in [("format", &p.format), ("framing", &p.framing), ("handshake", &p.handshake)] { + out += &format!( + " {key:<10} {:<24} {}\n", + choice_value(choice), + style(DIM, &source(choice.source, key)) + ); + } + let reason = p.guarantees.iter().find_map(|g| g.reason.as_deref()); + if p.unverified { + match reason { + Some(reason) => out += &format!(" {:<10} \"{reason}\"\n", "unverified"), + None => out += &format!(" {:<10} (no reason given)\n", "unverified"), + } + } + + out += "\n"; + for row in &p.guarantees { + let (mark, state) = match (row.held, plain) { + (true, true) => ("", style(HELD, "held")), + (true, false) => ("✓ ", style(HELD, "held")), + (false, true) => ("", style(GIVEN_UP, "given up")), + (false, false) => ("✗ ", style(GIVEN_UP, "given up")), + }; + out += &format!(" {mark}{:<22} {state}\n", row.guarantee.describe()); + for cause in &row.causes { + out += &format!(" because {cause}\n"); + } + match &row.acceptance { + Acceptance::NotNeeded => {} + Acceptance::Unknown => { + out += &format!(" {}\n", style(DIM, "acceptance: up to each consumer's config.idp")); + } + Acceptance::Accepted(by) => out += &format!(" accepted by {by}\n"), + Acceptance::NotAccepted => out += &format!(" {}\n", style(GIVEN_UP, "not accepted")), + } + } + out +} + +/// `jsonrpc (method_names = qualified)`. +fn choice_value(choice: &Choice) -> String { + if choice.params.is_empty() { + return choice.value.clone(); + } + let params: Vec = choice.params.iter().map(|(k, v)| format!("{k} = {v}")).collect(); + format!("{} ({})", choice.value, params.join(", ")) +} + +/// Where a choice came from, for the header. +fn source(source: Source, key: &str) -> String { + match source { + Source::Annotation => format!("from @{key}"), + Source::PackageDefault => match key { + "handshake" => "from config.idp `handshake`".to_string(), + _ => format!("from config.idp `wire.{key}`"), + }, + Source::CoreDefault => "core's default".to_string(), + Source::Framing => "JSON-RPC's format".to_string(), + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 78113e3..a2b43c4 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -10,6 +10,7 @@ pub mod completions; pub mod conformance; pub mod diff; pub mod generate; +pub mod inspect; pub mod new; pub mod release; pub mod reset; diff --git a/src/main.rs b/src/main.rs index e46840d..e18c213 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,6 +118,7 @@ fn run(cli: Cli) -> Result<()> { match cli.command { Commands::Build { watch } => commands::build::run(&work_dir, watch), Commands::Check => commands::check::run(&work_dir), + Commands::Inspect { json } => commands::inspect::run(&work_dir, json), Commands::Generate { target, out, diff --git a/tests/cli/inspect.rs b/tests/cli/inspect.rs new file mode 100644 index 0000000..0a05e15 --- /dev/null +++ b/tests/cli/inspect.rs @@ -0,0 +1,166 @@ +//! `comline inspect` + +use std::fs; +use std::path::Path; + +use predicates::prelude::*; + +use crate::util::*; + +fn package(dir: &Path, config: &str, schema: &str) { + fs::create_dir_all(dir.join("src")).unwrap(); + let name = dir.file_name().unwrap().to_str().unwrap(); + fs::write( + dir.join("config.idp"), + format!("congregation {name}\nspecification_version = 1\n{config}"), + ) + .unwrap(); + fs::write(dir.join("src/api.ids"), schema).unwrap(); +} + +const ALLOW: &str = "settings = { annotations = { unverified = { allowed = true } framing = { allowed = true } } }\n"; + +fn shop(temp: &Path) -> std::path::PathBuf { + let shop = temp.join("shop"); + package( + &shop, + &format!("wire = {{ framing = \"datagram@1\" }}\n{ALLOW}"), + "protocol Orders {\n function place(id: u64) -> bool;\n}\n\n\ + @framing = { name = \"jsonrpc\", method_names = \"qualified\" }\n\ + @handshake = \"none\"\n\ + @unverified = \"the 2009 gateway can't run a handshake\"\n\ + protocol Legacy {\n function ping();\n}\n", + ); + shop +} + +#[test] +fn shows_each_choice_its_source_and_every_guarantee() { + let temp = tempfile::tempdir().unwrap(); + let shop = shop(temp.path()); + + let out = comline_cmd() + .current_dir(&shop) + .args(["--plain", "inspect"]) + .assert() + .success() + .get_output() + .stdout + .clone(); + let out = String::from_utf8(out).unwrap(); + let expected = "\ +protocol api::Orders (shop) + format packed@1 core's default + framing datagram from config.idp `wire.framing` + handshake comline@1 core's default + + schema agreement held + encoding agreement held + encoding conformance held + version honesty held + +protocol api::Legacy (shop) + format json@1 JSON-RPC's format + framing jsonrpc (method_names = qualified) from @framing + handshake none from @handshake + unverified \"the 2009 gateway can't run a handshake\" + + schema agreement given up + because handshake `none` + acceptance: up to each consumer's config.idp + encoding agreement given up + because handshake `none` + acceptance: up to each consumer's config.idp + encoding conformance held + version honesty held +"; + assert_eq!(out, expected); + assert!(out.lines().all(|l| l.chars().count() <= 80), "{out}"); + assert!(!shop.join(".comline").exists(), "inspect must not create the CAS store"); +} + +#[test] +fn json_carries_the_same() { + let temp = tempfile::tempdir().unwrap(); + let shop = shop(temp.path()); + + let out = comline_cmd() + .current_dir(&shop) + .args(["inspect", "--json"]) + .assert() + .success() + .get_output() + .stdout + .clone(); + let json: serde_json::Value = serde_json::from_slice(&out).unwrap(); + let legacy = &json["protocols"][1]; + assert_eq!(legacy["name"], "Legacy"); + assert_eq!(legacy["framing"]["source"], "annotation"); + assert_eq!(legacy["framing"]["params"]["method_names"], "qualified"); + assert_eq!(legacy["format"]["source"], "framing"); + assert_eq!(json["protocols"][0]["framing"]["source"], "package_default"); + assert_eq!(json["protocols"][0]["format"]["source"], "core_default"); + let row = &legacy["guarantees"][0]; + assert_eq!(row["guarantee"], "schema_agreement"); + assert_eq!(row["held"], false); + assert_eq!(row["causes"], serde_json::json!(["handshake `none`"])); + assert_eq!(row["reason"], "the 2009 gateway can't run a handshake"); + assert_eq!(row["acceptance"], serde_json::json!({ "state": "unknown" })); + assert_eq!(legacy["guarantees"][3]["acceptance"], serde_json::json!({ "state": "not_needed" })); +} + +#[test] +fn a_dependencys_protocol_shows_who_accepted_it() { + let temp = tempfile::tempdir().unwrap(); + package( + &temp.path().join("billing"), + &format!("handshake = \"none\"\n{ALLOW}"), + "@unverified = \"checked out of band\"\nprotocol Billing {\n function charge();\n}\n", + ); + let app = temp.path().join("app"); + package( + &app, + "dependencies = {\n billing = {\n path = \"../billing\"\n \ + accept_unverified = { Billing = [\"schema_agreement\", \"encoding_agreement\"] }\n }\n}\n", + "struct A {\n x: u32\n}\n", + ); + + comline_cmd() + .current_dir(&app) + .args(["--plain", "inspect"]) + .assert() + .success() + .stdout(predicate::str::contains("protocol billing::api::Billing (dependency billing)")) + .stdout(predicate::str::contains("handshake none from config.idp `handshake`")) + .stdout(predicate::str::contains(" because handshake `none`\n accepted by app\n")); +} + +#[test] +fn a_project_without_protocols_says_so() { + let temp = tempfile::tempdir().unwrap(); + let app = temp.path().join("app"); + package(&app, "", "struct A {\n x: u32\n}\n"); + + comline_cmd() + .current_dir(&app) + .args(["--plain", "inspect"]) + .assert() + .success() + .stdout("") + .stderr(predicate::str::contains("No protocols to inspect")); +} + +#[test] +fn reports_a_broken_schema() { + let temp = tempfile::tempdir().unwrap(); + let project = fixture_project(temp.path()); + fs::write(project.join("src/api.ids"), "struct Broken {").unwrap(); + + comline_cmd() + .current_dir(&project) + .arg("inspect") + .assert() + .failure() + .code(1) + .stderr(predicate::str::contains("validation failed")); +} diff --git a/tests/cli/main.rs b/tests/cli/main.rs index c408d87..0c1d8b1 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -16,6 +16,7 @@ mod diff; mod end_to_end; mod external; mod generate; +mod inspect; mod global; mod new; mod release; diff --git a/tests/fixtures/generators/Cargo.lock b/tests/fixtures/generators/Cargo.lock index 2e3ff69..05a057e 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=de7ceb4c266d1fb5d6dc3c918d84cb226003f6b6#de7ceb4c266d1fb5d6dc3c918d84cb226003f6b6" +source = "git+https://github.com/ComlineProject/generation?rev=0601f004302ebb93e6804732419ccf459902e84a#0601f004302ebb93e6804732419ccf459902e84a" dependencies = [ "comline-core", "eyre", @@ -87,7 +87,7 @@ dependencies = [ [[package]] name = "comline-codegen-rust" version = "0.1.0" -source = "git+https://github.com/ComlineProject/comline-rust?rev=5346678b82fc275fca741bf5ca0cfad1e8edf075#5346678b82fc275fca741bf5ca0cfad1e8edf075" +source = "git+https://github.com/ComlineProject/comline-rust?rev=6e8715ac126ab78316dd6a2712165d716351fab1#6e8715ac126ab78316dd6a2712165d716351fab1" dependencies = [ "comline-codegen", "comline-core", @@ -97,7 +97,7 @@ dependencies = [ [[package]] name = "comline-core" version = "0.1.0" -source = "git+https://github.com/ComlineProject/core?rev=2ae998634bad418e4a03ce7494e673275dafcc01#2ae998634bad418e4a03ce7494e673275dafcc01" +source = "git+https://github.com/ComlineProject/core?rev=47066583002dbca27975827106f9e34761d0dc64#47066583002dbca27975827106f9e34761d0dc64" dependencies = [ "blake3", "ciborium", @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "comline-core-stdlib" version = "0.1.0" -source = "git+https://github.com/ComlineProject/core?rev=2ae998634bad418e4a03ce7494e673275dafcc01#2ae998634bad418e4a03ce7494e673275dafcc01" +source = "git+https://github.com/ComlineProject/core?rev=47066583002dbca27975827106f9e34761d0dc64#47066583002dbca27975827106f9e34761d0dc64" [[package]] name = "comline-test-generators" diff --git a/tests/fixtures/generators/Cargo.toml b/tests/fixtures/generators/Cargo.toml index 9eb1857..bda1f1b 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 = "de7ceb4c266d1fb5d6dc3c918d84cb226003f6b6" } -comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "5346678b82fc275fca741bf5ca0cfad1e8edf075" } +comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "0601f004302ebb93e6804732419ccf459902e84a" } +comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "6e8715ac126ab78316dd6a2712165d716351fab1" } From 009499df7900c29d1944b64fb199b8abcfdd4d35 Mon Sep 17 00:00:00 2001 From: Kinflou Date: Sun, 1 Nov 2026 06:30:06 +0800 Subject: [PATCH 2/2] inspect: rustfmt --- src/commands/inspect.rs | 49 +++++++++++++++++++++++++++++++++-------- tests/cli/inspect.rs | 22 +++++++++++++----- tests/cli/main.rs | 2 +- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/commands/inspect.rs b/src/commands/inspect.rs index a0c051e..c2ee2eb 100644 --- a/src/commands/inspect.rs +++ b/src/commands/inspect.rs @@ -33,8 +33,12 @@ pub fn run(work_dir: &Path, json: bool) -> Result<()> { let mut out = anstream::stdout().lock(); if json { let report = serde_json::json!({ "protocols": protocols }); - writeln!(out, "{}", serde_json::to_string_pretty(&report).into_diagnostic()?) - .into_diagnostic()?; + writeln!( + out, + "{}", + serde_json::to_string_pretty(&report).into_diagnostic()? + ) + .into_diagnostic()?; return Ok(()); } @@ -42,7 +46,11 @@ pub fn run(work_dir: &Path, json: bool) -> Result<()> { ui::note("No protocols to inspect"); return Ok(()); } - let text = protocols.iter().map(|p| render(p, ui::plain())).collect::>().join("\n"); + let text = protocols + .iter() + .map(|p| render(p, ui::plain())) + .collect::>() + .join("\n"); write!(out, "{text}").into_diagnostic()?; Ok(()) } @@ -50,16 +58,30 @@ pub fn run(work_dir: &Path, json: bool) -> Result<()> { /// One protocol: a header of choices, then a row per guarantee. Fits 80 /// columns: anything long goes on its own indented line. pub fn render(p: &ProtocolSummary, plain: bool) -> String { - let style = |s: Style, text: &str| if plain { text.to_string() } else { format!("{s}{text}{s:#}") }; + let style = |s: Style, text: &str| { + if plain { + text.to_string() + } else { + format!("{s}{text}{s:#}") + } + }; let mut out = String::new(); - let whose = if p.dependency { format!("dependency {}", p.package) } else { p.package.clone() }; + let whose = if p.dependency { + format!("dependency {}", p.package) + } else { + p.package.clone() + }; out += &format!( "{} {}\n", style(BOLD, &format!("protocol {}::{}", p.schema, p.name)), style(DIM, &format!("({whose})")) ); - for (key, choice) in [("format", &p.format), ("framing", &p.framing), ("handshake", &p.handshake)] { + for (key, choice) in [ + ("format", &p.format), + ("framing", &p.framing), + ("handshake", &p.handshake), + ] { out += &format!( " {key:<10} {:<24} {}\n", choice_value(choice), @@ -89,10 +111,15 @@ pub fn render(p: &ProtocolSummary, plain: bool) -> String { match &row.acceptance { Acceptance::NotNeeded => {} Acceptance::Unknown => { - out += &format!(" {}\n", style(DIM, "acceptance: up to each consumer's config.idp")); + out += &format!( + " {}\n", + style(DIM, "acceptance: up to each consumer's config.idp") + ); } Acceptance::Accepted(by) => out += &format!(" accepted by {by}\n"), - Acceptance::NotAccepted => out += &format!(" {}\n", style(GIVEN_UP, "not accepted")), + Acceptance::NotAccepted => { + out += &format!(" {}\n", style(GIVEN_UP, "not accepted")) + } } } out @@ -103,7 +130,11 @@ fn choice_value(choice: &Choice) -> String { if choice.params.is_empty() { return choice.value.clone(); } - let params: Vec = choice.params.iter().map(|(k, v)| format!("{k} = {v}")).collect(); + let params: Vec = choice + .params + .iter() + .map(|(k, v)| format!("{k} = {v}")) + .collect(); format!("{} ({})", choice.value, params.join(", ")) } diff --git a/tests/cli/inspect.rs b/tests/cli/inspect.rs index 0a05e15..bf1e3d5 100644 --- a/tests/cli/inspect.rs +++ b/tests/cli/inspect.rs @@ -76,7 +76,10 @@ protocol api::Legacy (shop) "; assert_eq!(out, expected); assert!(out.lines().all(|l| l.chars().count() <= 80), "{out}"); - assert!(!shop.join(".comline").exists(), "inspect must not create the CAS store"); + assert!( + !shop.join(".comline").exists(), + "inspect must not create the CAS store" + ); } #[test] @@ -106,7 +109,10 @@ fn json_carries_the_same() { assert_eq!(row["causes"], serde_json::json!(["handshake `none`"])); assert_eq!(row["reason"], "the 2009 gateway can't run a handshake"); assert_eq!(row["acceptance"], serde_json::json!({ "state": "unknown" })); - assert_eq!(legacy["guarantees"][3]["acceptance"], serde_json::json!({ "state": "not_needed" })); + assert_eq!( + legacy["guarantees"][3]["acceptance"], + serde_json::json!({ "state": "not_needed" }) + ); } #[test] @@ -130,9 +136,15 @@ fn a_dependencys_protocol_shows_who_accepted_it() { .args(["--plain", "inspect"]) .assert() .success() - .stdout(predicate::str::contains("protocol billing::api::Billing (dependency billing)")) - .stdout(predicate::str::contains("handshake none from config.idp `handshake`")) - .stdout(predicate::str::contains(" because handshake `none`\n accepted by app\n")); + .stdout(predicate::str::contains( + "protocol billing::api::Billing (dependency billing)", + )) + .stdout(predicate::str::contains( + "handshake none from config.idp `handshake`", + )) + .stdout(predicate::str::contains( + " because handshake `none`\n accepted by app\n", + )); } #[test] diff --git a/tests/cli/main.rs b/tests/cli/main.rs index 0c1d8b1..c00249d 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -16,8 +16,8 @@ mod diff; mod end_to_end; mod external; mod generate; -mod inspect; mod global; +mod inspect; mod new; mod release; mod reset;