From b856571aa9aed02fc50722987d35dc2b3ce627ad Mon Sep 17 00:00:00 2001 From: meh Date: Sat, 25 Jul 2026 18:00:42 +0700 Subject: [PATCH 1/4] feat: fail generation on empty endpoint and enum-variant descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Descriptions in the RON are not cosmetic: an endpoint description becomes the MCP tool description an agent reads to decide whether to call the tool, and enum-variant descriptions are emitted into the generated JSON schemas and doc comments. A blank one produces a tool an agent cannot use correctly and a doc page that says nothing, with no signal at generation time. Generation now fails on missing or whitespace-only descriptions for endpoints (both EndpointSchema and EndpointSchemaList) and enum variants (both Enum and EnumList). Violations are collected across all files and reported together — one run tells you everything to fix, rather than one item per run — and each line names the file, the service and the item. `--allow-empty-descriptions` restores the previous behaviour for consumers not ready to annotate everything. Also stops emitting an empty `///` line for blank descriptions, which tripped clippy::empty_docs in downstream crates. This is fixed in both enum-emitting paths — EnumElement::to_rust_decl for RON enum definitions and Type::to_rust_decl for shared Type::Enum definitions — which are separate near-identical code paths, hence a test for each. Struct fields are deliberately not validated: Field.description is #[serde(skip)] upstream, so a RON file cannot express one. Verified end to end, not just by unit test: a RON with one blank endpoint description and one blank enum variant fails with both violations listed; --allow-empty-descriptions generates successfully; and the generated model.rs contains a doc comment for the documented variant, no doc line for the blank one, and zero bare `///` lines. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 6 +- Cargo.toml | 2 +- src/definitions.rs | 59 ++++++++++++++--- src/main.rs | 162 +++++++++++++++++++++++++++++++++++++++++++-- src/rust.rs | 50 +++++++++++--- 5 files changed, 251 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5fd3ca..2638e17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,7 +841,7 @@ dependencies = [ [[package]] name = "endpoint-gen" -version = "1.9.0" +version = "1.10.0" dependencies = [ "clap", "convert_case 0.10.0", @@ -873,9 +873,9 @@ dependencies = [ [[package]] name = "endpoint-libs" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a445d34b3a27b3096fd12c0db3d81810ddad22aa4af09a81178110c8ebab201" +checksum = "b422fc78a2f82f06c24df18ed57b6a0e4f60fd62abd032c245793d28675be676" dependencies = [ "alloy-primitives", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 6202025..d6b90fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "endpoint-gen" -version = "1.9.0" +version = "1.10.0" edition = "2024" repository = "https://github.com/pathscale/EndpointGen/" description = "Generate Rust code for websocket API endpoints" diff --git a/src/definitions.rs b/src/definitions.rs index 805afd1..e119dc6 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -137,19 +137,29 @@ impl ToRust for EnumElement { let mut fields = fields .iter() .map(|x| { - format!( - r#" + let variant_name = if x.name.chars().last().unwrap().is_lowercase() { + x.name.to_case(Case::Pascal) + } else { + x.name.clone() + }; + // A blank description must omit the `///` line entirely: an + // empty doc comment trips clippy::empty_docs downstream. + if x.description.trim().is_empty() { + format!( + r#" + {} = {} +"#, + variant_name, x.value + ) + } else { + format!( + r#" /// {} {} = {} "#, - x.description, - if x.name.chars().last().unwrap().is_lowercase() { - x.name.to_case(Case::Pascal) - } else { - x.name.clone() - }, - x.value - ) + x.description, variant_name, x.value + ) + } }) .sorted_by(|a, b| { // Sort by the endpoint code @@ -457,3 +467,32 @@ impl GenElement for EndpointSchemaListDefinition { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use endpoint_libs::model::EnumVariant; + + #[test] + fn enum_element_decl_omits_doc_comment_for_blank_descriptions() { + let element = EnumElement { + config: RustGenConfig::default(), + inner: Type::enum_( + "sample", + vec![ + EnumVariant::new_with_description("Documented", "Has docs.", 0), + EnumVariant::new("Bare", 1), + EnumVariant::new_with_description("Blank", " ", 2), + ], + ), + }; + let decl = element.to_rust_decl(false, false); + assert!(decl.contains("/// Has docs.")); + assert!( + !decl.lines().any(|l| l.trim() == "///"), + "empty doc comment emitted (clippy::empty_docs):\n{decl}" + ); + assert!(decl.contains("Bare = 1")); + assert!(decl.contains("Blank = 2")); + } +} diff --git a/src/main.rs b/src/main.rs index 473f910..4dac22a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,12 @@ struct Cli { /// Output directory for the generated files #[arg(short, long)] output_dir: Option, + + /// Allow endpoint and enum-variant descriptions to be missing or blank + /// (legacy behavior). By default, generation fails on empty descriptions + /// since they produce useless MCP tool metadata and docs. + #[arg(long)] + allow_empty_descriptions: bool, } fn main() -> Result<()> { @@ -61,7 +67,7 @@ fn main() -> Result<()> { let output_dir = generation_root.join("generated"); - let input_objects = build_object_lists(config_dir)?; + let input_objects = build_object_lists(config_dir, args.allow_empty_descriptions)?; let data = Data { project_root: generation_root, @@ -169,7 +175,66 @@ fn process_file(file_path: &Path) -> eyre::Result> { } } -fn process_input_files(dir: PathBuf) -> eyre::Result> { +/// Returns one violation string per missing/blank description in the +/// definition. Endpoint descriptions become MCP tool descriptions and doc +/// text; enum variant descriptions are emitted into the generated JSON +/// schemas — both are validated. +fn description_violations(definition: &Definition, path: &Path) -> Vec { + fn blank(s: &str) -> bool { + s.trim().is_empty() + } + + fn check_enum(inner: &Type, path: &Path, violations: &mut Vec) { + if let Type::Enum { name, variants } = inner { + for variant in variants { + if blank(&variant.description) { + violations.push(format!( + "{}: enum '{}' variant '{}': missing or empty description", + path.display(), + name, + variant.name + )); + } + } + } + } + + let mut violations = vec![]; + match definition { + Definition::EndpointSchema(def) => { + if blank(&def.schema.schema.description) { + violations.push(format!( + "{}: service '{}' endpoint '{}': missing or empty description", + path.display(), + def.service_name, + def.schema.schema.name + )); + } + } + Definition::EndpointSchemaList(def) => { + for endpoint in &def.endpoints { + if blank(&endpoint.schema.description) { + violations.push(format!( + "{}: service '{}' endpoint '{}': missing or empty description", + path.display(), + def.service_name, + endpoint.schema.name + )); + } + } + } + Definition::Enum(element) => check_enum(&element.inner, path, &mut violations), + Definition::EnumList(list) => { + for element in &list.enum_elements { + check_enum(&element.inner, path, &mut violations); + } + } + _ => {} + } + violations +} + +fn process_input_files(dir: PathBuf, allow_empty_descriptions: bool) -> eyre::Result> { let root = dir.as_path(); // Walk through the directory and all subdirectories @@ -185,10 +250,14 @@ fn process_input_files(dir: PathBuf) -> eyre::Result> { let mut rust_configs: Vec = vec![]; let mut valid_config_files_counter = 0u32; let mut config_errors = vec![]; + let mut description_errors = vec![]; for path in paths { match process_file(path.as_path()) { Ok(rust_config) => { if let Some(config) = rust_config { + if !allow_empty_descriptions { + description_errors.extend(description_violations(&config, path.as_path())); + } rust_configs.push(config); valid_config_files_counter += 1; } @@ -205,6 +274,16 @@ fn process_input_files(dir: PathBuf) -> eyre::Result> { bail!("Error processing RON config files:\n{}", config_errors.join("\n")); } + if !description_errors.is_empty() { + bail!( + "Empty-description validation failed for {} item(s). Every endpoint and enum variant \ + needs a description (it becomes MCP tool metadata and generated docs). \ + Pass --allow-empty-descriptions to bypass:\n{}", + description_errors.len(), + description_errors.join("\n") + ); + } + // If we haven't found any files, it's better to just return here immediately if valid_config_files_counter == 0 { bail!("No valid RON config files found in given path, aborting generation process"); @@ -220,8 +299,8 @@ struct InputObjects { error_codes: Vec, } -fn build_object_lists(dir: PathBuf) -> eyre::Result { - let rust_configs = process_input_files(dir)?; +fn build_object_lists(dir: PathBuf, allow_empty_descriptions: bool) -> eyre::Result { + let rust_configs = process_input_files(dir, allow_empty_descriptions)?; let mut service_schema_map: HashMap<(String, u16), Vec> = HashMap::new(); @@ -414,4 +493,79 @@ mod tests { assert_eq!(endpoint.errors[0].fields[0].name, "minLength"); assert_eq!(endpoint.errors[0].fields[1].name, "actualLength"); } + + use endpoint_gen::definitions::EndpointSchemaListDefinition; + use endpoint_libs::model::EnumVariant; + + fn endpoint_list(descriptions: &[&str]) -> Definition { + Definition::EndpointSchemaList(EndpointSchemaListDefinition { + service_name: "userApi".to_string(), + service_id: 6, + config: RustGenConfig::default(), + endpoints: descriptions + .iter() + .enumerate() + .map(|(i, desc)| EndpointSchemaElement { + frontend_facing: true, + config: RustGenConfig::default(), + schema: EndpointSchema::new(format!("Endpoint{i}"), 60000 + i as u32, vec![], vec![]) + .with_description(*desc), + }) + .collect(), + }) + } + + fn enum_definition(variant_descriptions: &[&str]) -> Definition { + Definition::Enum(EnumElement { + config: RustGenConfig::default(), + inner: Type::Enum { + name: "UserRole".to_string(), + variants: variant_descriptions + .iter() + .enumerate() + .map(|(i, desc)| EnumVariant::new_with_description(format!("Variant{i}"), desc.to_string(), i as i64)) + .collect(), + }, + }) + } + + #[test] + fn description_violations_flags_empty_and_whitespace_endpoints() { + let path = Path::new("config/schema_lists/060_user/061_user_api.ron"); + let violations = description_violations(&endpoint_list(&["Fetches a profile.", "", " \t"]), path); + assert_eq!(violations.len(), 2); + assert!(violations[0].contains("service 'userApi'")); + assert!(violations[0].contains("endpoint 'Endpoint1'")); + assert!(violations[0].contains("061_user_api.ron")); + assert!(violations[1].contains("endpoint 'Endpoint2'")); + } + + #[test] + fn description_violations_passes_documented_endpoints() { + let path = Path::new("config/a.ron"); + let violations = description_violations(&endpoint_list(&["Documented.", "Also documented."]), path); + assert!(violations.is_empty()); + } + + #[test] + fn description_violations_flags_blank_enum_variants() { + let path = Path::new("config/enums.ron"); + let violations = description_violations(&enum_definition(&["Platform admin", "", " "]), path); + assert_eq!(violations.len(), 2); + assert!(violations[0].contains("enum 'UserRole'")); + assert!(violations[0].contains("variant 'Variant1'")); + assert!(violations[1].contains("variant 'Variant2'")); + } + + #[test] + fn description_violations_ignores_structs_and_error_codes() { + // Struct fields cannot carry RON descriptions (Field.description is + // serde-skipped upstream), so StructList definitions never violate. + let path = Path::new("config/structs.ron"); + let def = Definition::StructList(endpoint_gen::definitions::StructListDefinition { + config: RustGenConfig::default(), + struct_elements: vec![], + }); + assert!(description_violations(&def, path).is_empty()); + } } diff --git a/src/rust.rs b/src/rust.rs index f44f451..0c78da2 100644 --- a/src/rust.rs +++ b/src/rust.rs @@ -101,19 +101,29 @@ impl ToRust for Type { let mut fields = fields .iter() .map(|x| { - format!( - r#" + let variant_name = if x.name.chars().last().unwrap().is_lowercase() { + x.name.to_case(Case::Pascal) + } else { + x.name.clone() + }; + // A blank description must omit the `///` line entirely: an + // empty doc comment trips clippy::empty_docs downstream. + if x.description.trim().is_empty() { + format!( + r#" + {} = {} +"#, + variant_name, x.value + ) + } else { + format!( + r#" /// {} {} = {} "#, - x.description, - if x.name.chars().last().unwrap().is_lowercase() { - x.name.to_case(Case::Pascal) - } else { - x.name.clone() - }, - x.value - ) + x.description, variant_name, x.value + ) + } }) .sorted_by(|a, b| { // Sort by the endpoint code @@ -564,6 +574,26 @@ mod tests { use crate::definitions::{EndpointSchemaElement, RustGenConfig}; use endpoint_libs::model::{EndpointSchema, Field}; + #[test] + fn enum_decl_omits_doc_comment_for_blank_descriptions() { + let e = Type::enum_( + "sample", + vec![ + EnumVariant::new_with_description("Documented", "Has docs.", 0), + EnumVariant::new("Bare", 1), + EnumVariant::new_with_description("Blank", " ", 2), + ], + ); + let decl = e.to_rust_decl(false, false); + assert!(decl.contains("/// Has docs.")); + assert!( + !decl.lines().any(|l| l.trim() == "///"), + "empty doc comment emitted (clippy::empty_docs):\n{decl}" + ); + assert!(decl.contains("Bare = 1")); + assert!(decl.contains("Blank = 2")); + } + fn test_data() -> Data { let user_info = Type::struct_( "UserInfo", From d9e67c38d72c718710fec335e4fa0250c9b2a75f Mon Sep 17 00:00:00 2001 From: meh Date: Sat, 25 Jul 2026 18:01:14 +0700 Subject: [PATCH 2/4] chore: move to endpoint-libs 2.0 (lockstep) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit endpoint-libs 2.0 makes several schema-model types #[non_exhaustive] so that OpenAPI/AsyncAPI emission can ship as a 2.1 minor rather than a 3.0. That forbids struct literals and exhaustive matches from outside that crate, which breaks endpoint-gen in exactly three places: - `From for EndpointSchema` was a field-by-field copy. It is now a move of the inner schema — which is what it always meant, and which also stops it silently dropping fields added later (it would have dropped the new `meta` field). - The `Type` match in `to_rust_ref` gains a wildcard arm that panics with an actionable message. Panicking is right: emitting Rust for a type this version does not understand would produce silently wrong generated code. - A test constructing `EndpointErrorSchema` by literal now uses the constructor added upstream for this purpose. The `[patch.crates-io]` override is TEMPORARY and must be removed once endpoint-libs 2.0 is published — endpoint-libs 2.0.0-alpha.1 is not on crates.io, so the pair cannot build together without it. Verified: endpoint-gen builds and all 10 tests pass against the local endpoint-libs 2.0.0-alpha.1. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 4 +--- Cargo.toml | 6 +++++- src/definitions.rs | 16 +++++----------- src/main.rs | 16 +++++++++------- src/rust.rs | 8 ++++++++ 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2638e17..00cdf39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,9 +873,7 @@ dependencies = [ [[package]] name = "endpoint-libs" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b422fc78a2f82f06c24df18ed57b6a0e4f60fd62abd032c245793d28675be676" +version = "2.0.0-alpha.1" dependencies = [ "alloy-primitives", "bytes", diff --git a/Cargo.toml b/Cargo.toml index d6b90fb..5dd968e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" [dependencies] # Internal dependencies -endpoint-libs = "1.9.0" +endpoint-libs = "2.0.0-alpha.1" endpoint-gen-macros = { path = "./endpoint-gen-macros", version = "1.3.4" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -30,3 +30,7 @@ smart-serde-default = "0.1" [build-dependencies] toml = "0.9" + +# TEMPORARY (2.0 lockstep verification) — remove before release. +[patch.crates-io] +endpoint-libs = { path = "../endpoint-libs" } diff --git a/src/definitions.rs b/src/definitions.rs index e119dc6..d10c739 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -427,17 +427,11 @@ pub struct EndpointSchemaElement { impl From for EndpointSchema { fn from(val: EndpointSchemaElement) -> Self { - EndpointSchema { - name: val.schema.name, - code: val.schema.code, - parameters: val.schema.parameters, - returns: val.schema.returns, - stream_response: val.schema.stream_response, - description: val.schema.description, - json_schema: val.schema.json_schema, - roles: val.schema.roles, - errors: val.schema.errors, - } + // Was a field-by-field copy, which `#[non_exhaustive]` (endpoint-libs 2.0) + // now forbids from outside that crate — and which was always a no-op, since + // the source field *is* an EndpointSchema. Moving it also means new fields + // (e.g. `meta`) are carried through automatically instead of being dropped. + val.schema } } diff --git a/src/main.rs b/src/main.rs index 4dac22a..e1ca110 100644 --- a/src/main.rs +++ b/src/main.rs @@ -469,15 +469,17 @@ mod tests { vec![Field::new("user_name", Type::String)], vec![Field::new("access_token", Type::String)], ) - .with_errors(vec![EndpointErrorSchema { - name: "PasswordTooShort".to_string(), - code: EndpointErrorCodeRef::new("BadRequest"), - message: "Password too short".to_string(), - fields: vec![ + .with_errors(vec![ + EndpointErrorSchema::new( + "PasswordTooShort", + EndpointErrorCodeRef::new("BadRequest"), + ) + .with_message("Password too short") + .with_fields(vec![ Field::new("min_length", Type::Int32), Field::new("actual_length", Type::Int32), - ], - }]), + ]), + ]), }], )], enums: vec![], diff --git a/src/rust.rs b/src/rust.rs index 0c78da2..a6263a3 100644 --- a/src/rust.rs +++ b/src/rust.rs @@ -55,6 +55,14 @@ impl ToRust for Type { Type::BlockchainTransactionHash if serde_with => "H256".to_owned(), Type::BlockchainAddress => "BlockchainAddress".to_owned(), Type::BlockchainTransactionHash => "BlockchainTransactionHash".to_owned(), + // `Type` is #[non_exhaustive] as of endpoint-libs 2.0, so a newer libs + // release can add variants without breaking this build. Panicking is the + // right behaviour: emitting Rust for a type we do not understand would + // produce silently wrong generated code. + other => panic!( + "endpoint-gen does not know how to emit Rust for {other:?}; \ + upgrade endpoint-gen to match your endpoint-libs version" + ), } } From 9c710a20e37ec369f1976599072b2f18d181abc4 Mon Sep 17 00:00:00 2001 From: meh Date: Sat, 25 Jul 2026 18:03:12 +0700 Subject: [PATCH 3/4] feat: extend empty-description validation to error codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit validated endpoints and enum variants but skipped ErrorCodeList through a catch-all `_ => {}` arm. That looks like an oversight rather than a decision: the test asserting the exclusion was named `..._ignores_structs_and_error_codes` but only exercised structs, and its comment justified only the struct case. Error-code descriptions are load-bearing in the same way. They become the doc comments on the generated `EnumErrorCode` variants (via EnumVariant::new_with_description in rust.rs) and the third column of docs/error_codes/error_codes.md. A blank one yields an error a caller cannot interpret from either artifact. - `description_violations` now reports blank error-code descriptions, naming the code and its numeric value. - The catch-all arm is replaced with an explicit `Struct | StructList` arm, so a future Definition variant is a compile error here rather than a silent gap — the same failure mode this commit is fixing. - The misnamed test is split into `..._ignores_structs` (unchanged behaviour, honest name) and a new `..._flags_blank_error_codes`. - The top-level failure message now names all three validated kinds, since it previously understated what runs. Verified end to end: a RON with a blank error-code description fails with the code and number named; documenting it generates successfully and the text appears in both error_codes.md and the generated model. Co-Authored-By: Claude Fable 5 --- src/main.rs | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index e1ca110..6b925c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -229,7 +229,25 @@ fn description_violations(definition: &Definition, path: &Path) -> Vec { check_enum(&element.inner, path, &mut violations); } } - _ => {} + Definition::ErrorCodeList(list) => { + // Error-code descriptions are not cosmetic either: they become the doc + // comments on the generated `EnumErrorCode` variants and the third + // column of docs/error_codes/error_codes.md. A blank one produces an + // error a caller cannot interpret. + for code in &list.codes { + if blank(&code.description) { + violations.push(format!( + "{}: error code '{}' ({}): missing or empty description", + path.display(), + code.name, + code.code + )); + } + } + } + // Struct fields cannot carry RON descriptions — Field.description is + // #[serde(skip)] upstream — so struct definitions can never violate. + Definition::Struct(_) | Definition::StructList(_) => {} } violations } @@ -276,9 +294,10 @@ fn process_input_files(dir: PathBuf, allow_empty_descriptions: bool) -> eyre::Re if !description_errors.is_empty() { bail!( - "Empty-description validation failed for {} item(s). Every endpoint and enum variant \ - needs a description (it becomes MCP tool metadata and generated docs). \ - Pass --allow-empty-descriptions to bypass:\n{}", + "Empty-description validation failed for {} item(s). Every endpoint, enum variant \ + and error code needs a description (these become MCP tool metadata, generated doc \ + comments and the error-code reference). Pass --allow-empty-descriptions to \ + bypass:\n{}", description_errors.len(), description_errors.join("\n") ); @@ -560,7 +579,7 @@ mod tests { } #[test] - fn description_violations_ignores_structs_and_error_codes() { + fn description_violations_ignores_structs() { // Struct fields cannot carry RON descriptions (Field.description is // serde-skipped upstream), so StructList definitions never violate. let path = Path::new("config/structs.ron"); @@ -570,4 +589,22 @@ mod tests { }); assert!(description_violations(&def, path).is_empty()); } + + #[test] + fn description_violations_flags_blank_error_codes() { + use endpoint_gen::definitions::{ErrorCodeListDefinition, ErrorCodeSchema}; + let path = Path::new("config/error_codes.ron"); + let def = Definition::ErrorCodeList(ErrorCodeListDefinition { + codes: vec![ + ErrorCodeSchema::new("BadRequest", 400, "The request was malformed."), + ErrorCodeSchema::new("Teapot", 418, ""), + ErrorCodeSchema::new("Blank", 419, " \t "), + ], + }); + let violations = description_violations(&def, path); + assert_eq!(violations.len(), 2, "{violations:?}"); + assert!(violations[0].contains("error code 'Teapot' (418)"), "{violations:?}"); + assert!(violations[1].contains("error code 'Blank' (419)"), "{violations:?}"); + assert!(violations[0].contains("error_codes.ron")); + } } From bb41d732e703a37d5f85ee9c4dddcd2f5d26d6b4 Mon Sep 17 00:00:00 2001 From: meh Date: Sat, 25 Jul 2026 18:23:59 +0700 Subject: [PATCH 4/4] chore: depend on published endpoint-libs 2.0.0-alpha.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the temporary [patch.crates-io] override. endpoint-libs 2.0.0-alpha.1 is now on crates.io, so the pair resolves normally — Cargo.lock records `source = "registry+https://github.com/rust-lang/crates.io-index"` rather than a local path. Note the exact-version requirement is deliberate: 2.0.0-alpha.1 is a pre-release, and a plain "2.0" requirement would not match it. Verified against the published crate: builds, 11 tests green, clippy clean. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 2 ++ Cargo.toml | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00cdf39..2751bdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,6 +874,8 @@ dependencies = [ [[package]] name = "endpoint-libs" version = "2.0.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01f4bd8850b41dd1cb2df5a5a32f5bc85c295903b55d62941424e0009d2e7c0" dependencies = [ "alloy-primitives", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 5dd968e..d51abd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,3 @@ smart-serde-default = "0.1" [build-dependencies] toml = "0.9" - -# TEMPORARY (2.0 lockstep verification) — remove before release. -[patch.crates-io] -endpoint-libs = { path = "../endpoint-libs" }