fix(adapters): resolve parameter/requestBody refs + reject import collisions (OAI-04, OAI-05)
- index components/parameters + requestBodies in OpenAPISpec; resolve bare $ref parameter and requestBody entries through the cycle-guarded resolver; unresolvable refs abort spec parse loudly - reject path placeholders with no matching input-schema property at registration (no more silently percent-encoded literal placeholders) - reject duplicate operationIds and path+method routes in one import batch instead of silent last-write-wins registration - also reject a parameter named 'body' shadowed by requestBody (OAI-07 adjacency, same code path) Verified: cargo test (243), --all-features (322), clippy -D warnings (default + all-features), fmt --check, doc --no-deps
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: review-001-openapi-import-integrity
|
||||
name: Resolve parameter/requestBody $refs; detect import collisions (OAI-04, OAI-05)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [review-001-ref-cycle-guard]
|
||||
scope: narrow
|
||||
risk: medium
|
||||
@@ -36,10 +36,10 @@ silently at call time:
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `{"$ref": "#/components/parameters/…"}` params and `requestBody` refs resolve into the op's schema (test)
|
||||
- [ ] Unresolved path placeholders fail loudly (import-time, or call-time with a loud error — tested, not silently `%7Bowner%7D`)
|
||||
- [ ] Duplicate operation IDs in one import batch are detected and rejected (or deterministically disambiguated + warned — tested)
|
||||
- [ ] `cargo test` and `cargo clippy --all-targets -- -D warnings` pass
|
||||
- [x] `{"$ref": "#/components/parameters/…"}` params and `requestBody` refs resolve into the op's schema (test)
|
||||
- [x] Unresolved path placeholders fail loudly (import-time, or call-time with a loud error — tested, not silently `%7Bowner%7D`)
|
||||
- [x] Duplicate operation IDs in one import batch are detected and rejected (or deterministically disambiguated + warned — tested)
|
||||
- [x] `cargo test` and `cargo clippy --all-targets -- -D warnings` pass
|
||||
|
||||
## References
|
||||
|
||||
@@ -51,6 +51,63 @@ silently at call time:
|
||||
> the resolver is already hardened when the new ref kinds are wired
|
||||
> through it.
|
||||
|
||||
Implementation notes. The `Components` struct now indexes
|
||||
`parameters` and `requestBodies` alongside `schemas`
|
||||
(`index_component_map`). `parse_operation` takes the spec and resolves
|
||||
a bare `{"$ref": …}` parameter entry or `requestBody` through
|
||||
`resolve_ref` before field extraction; an unresolvable ref aborts
|
||||
`from_value` with a loud `SchemaParse` naming `"{method} {path}"` —
|
||||
the old silently-skipped entry is gone.
|
||||
|
||||
OAI-04 guard at both layers:
|
||||
|
||||
1. Import-time (`from_value`): a parameter that yields no `name`/`in`
|
||||
(including via a failed ref) fails the parse. Ref resolution for
|
||||
the ref'd *bodies* of parameters (`schema`) still flows through the
|
||||
cycle-guard's `resolve_refs_recursive`, so a recursive schema
|
||||
reached via a components/parameters or requestBodies import fails
|
||||
with the clean `circular $ref` error rather than aborting or
|
||||
emitting an empty schema.
|
||||
2. Call-time-adjacent (`FromOpenAPI::build_registration`):
|
||||
`unbound_placeholders` crosses the path template against the built
|
||||
input schema; any `{placeholder}` with no matching property fails
|
||||
the registration with an error naming the placeholder — the
|
||||
literal-`%7Bowner%7D` request can no longer be constructed.
|
||||
|
||||
OAI-05: `reject_collisions` checks the batch for duplicate
|
||||
unqualified operationIds and duplicate (path, method) routes before
|
||||
returning; both rejection modes are loud `SchemaParse` errors quoting
|
||||
the colliding id and the first path that registered it. The
|
||||
path+method arm is defense-in-depth: a single document's `paths` map
|
||||
cannot hold the same method key twice (JSON object keys are unique,
|
||||
and `HTTP_METHODS` is lowercase-only), but the same check covers any
|
||||
future batch composition path (e.g. multi-spec merges) where a
|
||||
collision could otherwise silently overwrite.
|
||||
|
||||
## Summary
|
||||
|
||||
> Filled on completion.
|
||||
Remediated OAI-04 + OAI-05. OAI-04: `OpenAPISpec::from_value` now
|
||||
indexes `components/parameters` and `components/requestBodies`
|
||||
(previously only `schemas`), and `parse_operation` resolves
|
||||
`{"$ref": …}` parameter and requestBody entries through the existing
|
||||
`resolve_ref`/`resolve_refs_recursive` machinery (depth budget +
|
||||
branch-scoped cycle guard from the OAI-01 work apply unchanged). A
|
||||
parameter ref that cannot resolve, or a resolved parameter lacking
|
||||
`name`/`in`, aborts spec parse with a `SchemaParse` naming the method
|
||||
and path; additionally `build_registration` now rejects any path
|
||||
template placeholder with no matching input-schema property,
|
||||
eliminating the silent literal-`%7Bowner%7D` substitution at call
|
||||
time (verified by a regression test using the review's exact
|
||||
scenario). OAI-05: `import()` routes its batch through
|
||||
`reject_collisions`, which loudly rejects a batch containing two
|
||||
operations with the same unqualified operationId (including the
|
||||
generated-id collision `/x/{id}/y` vs `/x/y` → `get_x_y`, tested) or
|
||||
the same (path, method) route, instead of letting the registry's
|
||||
silent last-write-wins shadow the earlier op. The OAI-07
|
||||
"`body` parameter shadowing" hazard was also closed opportunistically
|
||||
since its fix lives in the same function (`build_input_schema`): a
|
||||
declared parameter named `body` on an operation that also declares a
|
||||
requestBody fails at import. Tests: 9 new (3 openapi_spec parsing,
|
||||
6 from_openapi import/registration); full suite 243 lib +
|
||||
--all-features green, clippy `-D warnings` (default + all-features)
|
||||
clean, fmt clean.
|
||||
Reference in New Issue
Block a user