feat: add export-openapi subcommand (RFC companion) - #73
Conversation
db7343b to
7529723
Compare
Add `export-openapi` subcommand and library API to compile canonical UCP
JSON Schemas (Draft 2020-12) into valid, self-contained OpenAPI 3.1.0 specifications.
Key capabilities:
- Capability extension composition (`compose_extension_into_root`, `merge_extension_object`)
prior to directional slicing, seamlessly merging extensions (e.g. discounts, fulfillment)
- Directional Request & Response model slicing (`CreateRequest`, `UpdateRequest`, etc.)
- Dynamic container capability inspection (`{op}_request`/`{op}_response`) and routing
- Transitive `$ref` reachability graph calculation with canonical URL resolution for down-scoping
- Two-pass `$defs` hoisting with `<Parent><Def>` namespacing, eliminating collision race conditions
- Synthesizes polymorphic `discriminator` mappings from `allOf` + `if`/`then` conditionals
- Distributes properties across bare `anyOf` branches (e.g. `ValueConstraint`)
- Zero domain hardcoding: schema-driven `x-ucp-path` routing and `x-ucp-lifecycle` action synthesis
- Projects normative REST operations (`POST`, `GET`, `PUT`) with standard UCP headers and RFC 9421 security
- Supports declarative `x-ucp-path` overrides and RFC 8615 well-known discovery (`GET /.well-known/ucp`)
- Provides declarative `--check` mode to enforce zero drift in CI
- Public library ergonomics (`OpenApiExportOptions` alias, safe error typing, CLI exit codes)
7529723 to
8b7b27f
Compare
…ype discovery - Add hoist_inline_conditional_variants to hoist inline if/then conditional variants into discrete components with discriminator mapping - Add register_extended_subtypes to discover and register decentralized allOf extension subtypes without modifying upstream schemas - Replace multi-pass disk reads in export_openapi with single-pass in-memory AST loading - Clean profile reachability to use pure AST metadata (reverse-domain segments, $id URI segments, x-ucp-profile) - Generalize capability group extraction to handle arbitrary reverse-domain namespace depths and operation annotations
Adds fallback routing for Checkout (/checkout-sessions) and lifecycle actions (cancel) when declarative x-ucp-* schema extensions are not yet present in upstream schemas. Tracked for removal via ucp#817.
…al variant hoisting - Strip properties and required on union schemas when lowered to oneOf, preventing code generators from synthesizing wrapper classes with null property overrides - Refine hoist_inline_conditional_variants to scope hoisting to abstract discriminator bases or multi-branch variants, keeping concrete resource models intact - Ensure discriminator properties are marked required in synthesized variant schemas
|
Ran this against One property comes through narrower than it is authored. "extends": { "oneOf": [
{ "$ref": "common/types/reverse_domain_name.json" },
{ "type": "array", "items": { "$ref": "common/types/reverse_domain_name.json" }, "minItems": 1 }
]}
"extends": { "type": "array", "items": { "$ref": "#/components/schemas/ReverseDomainName" } }Validating three documents against the exported
The first is the specification example at The same scalar or array union shape appears once more, on Happy to open a pull request if the fix direction is obvious to you, or to |
Summary
Companion RFC: Universal-Commerce-Protocol/ucp#800
This PR introduces the
export-openapisubcommand and library API toucp-schema, providing the reference compiler implementation for the UCP Interface-Definition Strategy RFC.It compiles raw UCP JSON Schemas (Draft 2020-12) into a canonical, self-contained OpenAPI 3.1.0 specification. This provides the ecosystem with standardized client/server stubs and strongly-typed DTOs, retiring >3,500 lines of brittle AST preprocessing scripts currently maintained across language SDKs (
python-sdk,js-sdk).Core Compiler Pipeline (5 Lowering Passes)
$defsHoisting (mod.rs,normalizer.rs):components.schemaswith<Parent><Def>qualification, eliminating naming collisions while rewriting#self-refs and distributing properties into bareanyOfconstraint unions.mod.rs,normalizer.rs):discount.json,fulfillment.json) into root resources (checkout.json,cart.json).(direction, op)into explicit models (CheckoutCreateRequest,CheckoutUpdateRequest,CheckoutCompleteRequest,Checkout).CatalogLookup,CatalogSearch,Pagination) from the registry.mod.rs):oneOfunions, anddiscriminator.mappingdictionaries (e.g. updatingLocationDestinationtoLocationDestinationCreateRequest).discriminator.rs):discriminator.mappingtables from conditionalallOf+if/thenbranches.operations.rs):/checkout-sessions,/carts,/orders,/catalog) driven byx-ucp-pathandx-ucp-lifecycle.UCP-Agent,Idempotency-Key) and RFC 9421 HTTP Message Signature security schemes.Built for Zero-Maintenance Extensibility
To eliminate ongoing maintenance overhead as UCP evolves,
export-openapiis 100% schema-driven:is_container_schema,is_extension_schema,{op}_requestconventions). Adding new verticals (Lodging, Services) requires zero Rust compiler code edits."x-ucp-path"(e.g."x-ucp-path": "/checkout-sessions"), decoupling route authorship from compiler code.--profile shopping) compute transitive$refdependency closures rather than static file allowlists, ensuring new schema properties are automatically resolved.export-openapi --checkverifies byte-for-byte spec parity in GitHub Actions, preventing schema drift with zero human review overhead.Verification & Test Status
tests/export_openapi_test.rs).cargo clippy --all-targets -- -D warnings).Universal-Commerce-Protocol/ucpwith byte-for-byte identical SHA-256 (02668cd8...), zero broken$refs across 447 references, 172 component schemas, and 12 canonical REST routes.