--- 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.