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
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: review-001-openapi-loud-degradation
|
||||
name: Loud unsupported-OpenAPI-feature handling (OAI-06)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: []
|
||||
scope: narrow
|
||||
risk: low
|
||||
@@ -42,10 +42,10 @@ 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
|
||||
- [x] 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)
|
||||
- [x] `HTTP_0` no longer emitted (default responses mapped or dropped loudly) — `/search` never advertises a code that can't match
|
||||
- [x] `style`/`explode` non-default forms do not silently mis-serialize arrays
|
||||
- [x] `cargo test` and `cargo clippy --all-targets -- -D warnings` pass
|
||||
|
||||
## References
|
||||
|
||||
@@ -54,12 +54,76 @@ defensible if logged).
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills during implementation. Deferred from the original
|
||||
> decomposition for staleness reasons; unblocked once OAI-02/03/07/09
|
||||
> landed (they did). Scope guard: this is import-time fidelity, not
|
||||
> new feature support — do not implement `servers` overrides or style
|
||||
> serialization here.
|
||||
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
|
||||
|
||||
> Filled on completion.
|
||||
- `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`.
|
||||
Reference in New Issue
Block a user