Problem
SpecGate specs determine API shape — the operation signature, calling convention, and parameter list are all driven by the spec YAML. When a spec models the wrong API shape (e.g., passing model context as a string parameter instead of using setup), the implementation faithfully produces a correct-but-unusable API.
This was observed in odata-rs where resolve_qualified_name(name, schemas_dsl_string) passes all spec tests but no real consumer would ever call it that way. The C# equivalent is IEdmModel.FindDeclaredType(name) — a method on a model object.
Proposal
Add spec-level signals (computable from the YAML alone) that guide toward good API design. Possible dimensions:
1. Input variance across cases
If a parameter has low variance across cases (e.g., schemas has 4 distinct values across 50 cases, while name has 48), it is likely immutable context that should be &self state rather than a function parameter.
2. Parameter reuse across operations
If the same parameter name appears in multiple operations within a spec (e.g., schemas in resolve_qualified_name, validate_name_scope, and validate_target_path), it is shared context that belongs on a struct — not passed separately to each function.
3. String-packed structure
If string inputs contain structural delimiters (;, |, ,) encoding data, the API is forcing callers to construct DSL strings instead of passing structured types. With v0.4.0 serde deserialization support, these should be #[derive(Deserialize)] types.
4. Runtime mutability from traces
If multi-step cases show a later step's output depending on an earlier step's mutation (step 1: add_schema → Ok, step 2: find_declared_type → Ok, where step 2 would fail without step 1), the traces reveal shared mutable state (&mut self). This could inform whether an operation should be a method vs free function.
Possible implementation
A specgate validate --check-api-shape pass that analyzes spec YAMLs and reports:
⚠ edm.names: parameter "schemas" appears in 3/5 operations
with only 4 distinct values across 50 cases
→ likely immutable context; consider setup/&self
⚠ edm.names: parameter "schemas" contains structural
delimiters (|;,) in 100% of values
→ consider structured YAML type instead of string
Design question: invisible setup
Currently setup: names an implementation function directly (setup: build_model), which leaks implementation details into the spec. If setup becomes invisible (the harness infers the split from the implementation's signature), these metrics become even more important — they would be the primary signal guiding implementors toward putting the right things on self vs as parameters.
Context
Discovered while working on odata-rs (OData v4.01 Rust implementation). The edm.names spec has 143 string-packed DSL inputs across 5 operations, with schemas/containers/nested_segments repeated throughout. The implementations pass all tests but produce APIs that no downstream consumer would use.
Problem
SpecGate specs determine API shape — the operation signature, calling convention, and parameter list are all driven by the spec YAML. When a spec models the wrong API shape (e.g., passing model context as a string parameter instead of using setup), the implementation faithfully produces a correct-but-unusable API.
This was observed in
odata-rswhereresolve_qualified_name(name, schemas_dsl_string)passes all spec tests but no real consumer would ever call it that way. The C# equivalent isIEdmModel.FindDeclaredType(name)— a method on a model object.Proposal
Add spec-level signals (computable from the YAML alone) that guide toward good API design. Possible dimensions:
1. Input variance across cases
If a parameter has low variance across cases (e.g.,
schemashas 4 distinct values across 50 cases, whilenamehas 48), it is likely immutable context that should be&selfstate rather than a function parameter.2. Parameter reuse across operations
If the same parameter name appears in multiple operations within a spec (e.g.,
schemasinresolve_qualified_name,validate_name_scope, andvalidate_target_path), it is shared context that belongs on a struct — not passed separately to each function.3. String-packed structure
If string inputs contain structural delimiters (
;,|,,) encoding data, the API is forcing callers to construct DSL strings instead of passing structured types. With v0.4.0 serde deserialization support, these should be#[derive(Deserialize)]types.4. Runtime mutability from traces
If multi-step cases show a later step's output depending on an earlier step's mutation (step 1:
add_schema→ Ok, step 2:find_declared_type→ Ok, where step 2 would fail without step 1), the traces reveal shared mutable state (&mut self). This could inform whether an operation should be a method vs free function.Possible implementation
A
specgate validate --check-api-shapepass that analyzes spec YAMLs and reports:Design question: invisible setup
Currently
setup:names an implementation function directly (setup: build_model), which leaks implementation details into the spec. If setup becomes invisible (the harness infers the split from the implementation's signature), these metrics become even more important — they would be the primary signal guiding implementors toward putting the right things onselfvs as parameters.Context
Discovered while working on
odata-rs(OData v4.01 Rust implementation). Theedm.namesspec has 143 string-packed DSL inputs across 5 operations, withschemas/containers/nested_segmentsrepeated throughout. The implementations pass all tests but produce APIs that no downstream consumer would use.