- 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
113 lines
5.5 KiB
Markdown
113 lines
5.5 KiB
Markdown
---
|
|
id: review-001-openapi-import-integrity
|
|
name: Resolve parameter/requestBody $refs; detect import collisions (OAI-04, OAI-05)
|
|
status: completed
|
|
depends_on: [review-001-ref-cycle-guard]
|
|
scope: narrow
|
|
risk: medium
|
|
impact: component
|
|
level: implementation
|
|
tags: [adapters, review-001, from-openapi]
|
|
---
|
|
|
|
## Description
|
|
|
|
Review 001 findings OAI-04 + OAI-05 — import produces ops that misbehave
|
|
silently at call time:
|
|
|
|
- **OAI-04** (`openapi_spec.rs:236-262`): parameter entries of the form
|
|
`{"$ref": "#/components/parameters/Id"}` (extremely common in real
|
|
specs) have no `name`, are silently skipped, and never reach
|
|
`resolve_refs_recursive`; same for `requestBody: {"$ref": …}`. Only
|
|
`components/schemas` is indexed (`:162-174`). Result: the op registers
|
|
with `{id}` in the template but `id` absent from the schema; at call
|
|
time the placeholder is substituted with the literal `{owner}` text and
|
|
percent-encoded (`%7Bowner%7D`) — a well-formed request to a nonsense
|
|
path, with credentials attached. No error at import or call time.
|
|
Fix: index `components/parameters` + `requestBodies`, resolve the ref
|
|
forms, and fail loudly on unresolved path placeholders (import and/or
|
|
call time).
|
|
- **OAI-05** (`from_openapi.rs:51-65,160`): generated operation IDs
|
|
collide by construction (`/x/{id}/y` and `/x/y` both → `get_x_y`);
|
|
the registry silently replaces, so one op shadows another and
|
|
`/search` under-reports. Detect duplicates within the import batch and
|
|
fail loudly (or deterministically disambiguate + warn — pick one and
|
|
document).
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [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
|
|
|
|
- docs/reviews/001-initial-implementation-review.md (Part E, OAI-04, OAI-05)
|
|
|
|
## Notes
|
|
|
|
> Agent fills during implementation. Follows the cycle-guard task so
|
|
> 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
|
|
|
|
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. |