Files
alkhttp/tasks/adapters/review-001-openapi-loud-degradation.md
T
glm-5.3-flash 7ce1ca6fbd feat(adapters): loud unsupported-OpenAPI-feature handling (OAI-06)
- build_error_schemas: default/wildcard response keys dropped with a
  warn instead of emitting a dead HTTP_0 ErrorDefinition — /search
  never advertises a code that can't match (the runtime mapper already
  synthesizes HTTP_<actual> for unmapped statuses)
- check_parameter_style: non-default style/explode parameter forms
  (spaceDelimited, pipeDelimited, deepObject, form+explode:false,
  simple+explode:true) fail import with a feature-naming SchemaParse;
  wire-equivalent defaults (form, simple) import unchanged — no more
  silent "[1,2]" array mis-serialization
- servers overrides rejected at import at all three levels (document,
  path, operation) — the adapter pins one base_url at assembly time
- trace-only paths: skip is now logged (warn naming path + methods),
  documented-as-inert instead of silent
- detect_op_type + build_output_schema sweep 2XX/default keys for
  text/event-stream — a default-declared SSE stream classifies as Sub
  instead of returning one giant text body

Tests: 11 new (error-drop, style rejections + default accept, servers
3-level rejections + baseline, trace skip, SSE default/2XX detection).
Verified: cargo test (299), --all-features (370 + suites), clippy
--all-targets -D warnings (default + all-features), fmt --check.

Tasks: review-001-openapi-loud-degradation
2026-08-30 08:10:40 +00:00

6.6 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
review-001-openapi-loud-degradation Loud unsupported-OpenAPI-feature handling (OAI-06) completed
narrow low component implementation
adapters
review-001
from-openapi
follow-up

Description

Review 001 finding OAI-06 — deliberately deferred during decomposition ("revisit after the input-schema work"): unsupported OpenAPI features still degrade silently, each producing an op that misbehaves only at call time (verified still true post-remediation):

  • default/wildcard response keys become ErrorDefinition { code: "HTTP_0", http_status: None } (from_openapi.rs:build_error_schemas, ~:238-243) — entries that never match a real status.
  • trace ops silently skipped (openapi_spec.rs); servers overrides ignored; parameter style/explode unsupported and silent (arrays serialize "[1,2]"); a default-declared SSE stream is missed by detect_op_type and would return one giant text string.

Since OAI-03's fix, the codebase already has the right pattern — in: cookie fails import with a clear SchemaParse naming the feature and the remediation. Extend that posture: unsupported features that would produce a wrong-behaving op fail import loudly (or degrade in a way that is documented, tested, and visibly warned), never silently.

Per-feature decision, implementer's judgment with the review's map: reject at import (cookie-style, preferred for anything that changes wire semantics: style/explode non-default forms, servers override on a non-matching base), or support (HTTP_0's real fix is mapping default → a documented catch-all code or dropping it with a warning — pick one and test it), or document-as-inert (trace skip is defensible if logged).

Acceptance Criteria

  • A spec using each unsupported feature either imports with a documented, warned, tested behavior or fails import with a feature-naming error (tests per feature)
  • HTTP_0 no longer emitted (default responses mapped or dropped loudly) — /search never advertises a code that can't match
  • style/explode non-default forms do not silently mis-serialize arrays
  • cargo test and cargo clippy --all-targets -- -D warnings pass

References

  • docs/reviews/001-initial-implementation-review.md (Part E, OAI-06)
  • tasks/adapters/review-001-input-schema-enforcement.md (the loud-unsupported pattern to extend)

Notes

Per-feature decisions (the review's map, applied):

  • default/wildcard responses → dropped loudly (warn, not reject): a catch-all code was considered and rejected — the runtime mapper (forward.rs::error_envelope) already synthesizes HTTP_<actual> for unmapped statuses, so a default ErrorDefinition could never match anything: keeping it would only let /search advertise a dead HTTP_0 code. build_error_schemas now skips any non-numeric response key at tracing::warn level, naming the operation, namespace, and response key, with the remediation in the message (declare explicit statuses). Wildcards (5XX, 4XX) fall out of the same parse::<u16>() check — they were the same dead entry class.
  • style/explode non-default forms → reject at import (cookie-style). New check_parameter_style in openapi_spec.rs; parse_operation now returns Result<Option<Operation>, ParameterStyleError> so the caller converts the refusal into a SchemaParse naming the parameter, the method+path, the offending declaration, and the remediation. Wire-equivalent forms accepted silently: form (query/path default, explode=true) and simple (header/path default, explode=false) — a spec authoring these explicitly gets identical serialization to omitting them. Rejected with feature-naming errors: spaceDelimited, pipeDelimited, deepObject, matrix, label (the generic arm), plus form+explode:false (comma-glue, the ?a=1,2 mis-serialization the review flagged) and simple+explode:true.
  • servers overrides → reject at import at all three levels (document root, per-path, per-operation) in one sweep in from_value. The adapter pins one base_url at assembly time and cannot honor per-location servers; the error names every offending location and the remediation (remove the entries, or one import per base URL). This check lives in openapi_spec.rs (parse time), so both from_openapi and any future raw-doc consumer inherit it.
  • trace → documented-as-inert, now logged. HTTP_METHODS remains without trace (scope guard: no new feature support), but a path entry carrying only unsupported methods was previously dropped without a trace; from_value now emits a tracing::warn naming the path and the skipped methods. Tested as "skips without erroring, the rest of the doc imports" (the log line itself is the visibility mechanism, consistent with the module's other warns).
  • default/non-200 2XX-declared SSE → supported (small superset). detect_op_type and build_output_schema sweep 200..206, 226, default for text/event-stream instead of only 200/201 — a default-declared stream now classifies as Sub with the SSE output schema instead of degrading to a giant single text body. This was the one place where "reject" would have been user-hostile: declaring streams under default is a real-world pattern, and detection is a two-line change.

Summary

  • src/adapters/from_openapi.rs: build_error_schemas drops default/wildcard keys loudly (never emits HTTP_0); detect_op_type
    • build_output_schema sweep 2XX/default for text/event-stream.
  • src/adapters/openapi_spec.rs: document/path/operation-level servers rejection; check_parameter_style (ParameterStyleError) rejecting non-default style/explode with feature-naming errors while accepting the wire-equivalent defaults; unsupported-method-only paths logged at warn.
  • Tests (8 new): default_response_key_is_dropped_not_advertised_as_http_0, default_declared_sse_stream_classifies_as_subscription, non_default_2xx_sse_stream_classifies_as_subscription, non_default_style_parameter_fails_import_naming_the_feature, deep_object_style_is_rejected_like_the_other_non_default_forms, form_style_with_explode_false_is_rejected, default_style_and_explode_forms_still_import, servers_override_at_document_level_fails_import, servers_override_at_path_and_operation_level_fails_import, servers_absent_baseline_still_imports, trace_only_path_is_skipped_and_documented_inert.
  • Verified: cargo test (299), --all-features (370 + suites), clippy --all-targets -- -D warnings (default + all-features), fmt --check.