Files
alkhttp/tasks/adapters/review-001-openapi-import-integrity.md
T
glm-5.3-flash 5df91cda25 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
2026-08-29 10:13:11 +00:00

5.5 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
review-001-openapi-import-integrity Resolve parameter/requestBody $refs; detect import collisions (OAI-04, OAI-05) completed
review-001-ref-cycle-guard
narrow medium component implementation
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

  • {"$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

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