Skip to content

Migrate to oas3 (OpenAPI 3.1 support) + header param Option fix#1

Open
chris13524 wants to merge 11 commits intomainfrom
oas3-migration
Open

Migrate to oas3 (OpenAPI 3.1 support) + header param Option fix#1
chris13524 wants to merge 11 commits intomainfrom
oas3-migration

Conversation

@chris13524
Copy link
Copy Markdown
Member

Summary

  • Migrates from openapiv3 to oas3 0.21.0 to support OpenAPI 3.1 specs end-to-end (entry points, validator, to_schema.rs, method.rs, util.rs).
  • Updates the sample_openapi fixtures from 3.0 to 3.1 and adds a coverage-gaps fixture (anyOf, oneOf+discriminator, additionalProperties).
  • Regenerates goldens for the new pipeline; updates CHANGELOG.
  • Bug fix: Header parameters with nullable schemas (type: ["string", "null"]) were generating field types like Result<Option<Option<String>>, String>. The Query parameter branch had logic to unwrap an already-optional inner type before the non-required wrapping in builder generation; this PR ports the same logic to the Header branch.
  • Pins workspace reqwest to 0.12 for compatibility with downstream consumers (e.g. yttrium) that are still on 0.12; this avoids two reqwest versions appearing in the same dep graph. Drop this commit if the fork prefers to stay on 0.13.

Test plan

  • cargo check -p progenitor -p progenitor-client -p progenitor-impl -p progenitor-macro -p cargo-progenitor (verified locally)
  • cargo test across the workspace
  • Regenerate via cargo-progenitor against an OpenAPI 3.1 spec with nullable header params and confirm the generated builder field is Result<Option<T>, String> (single Option)
  • Verify example-build, example-macro, example-out-dir still build (these may need their own reqwest version bumped/pinned to match)

🤖 Generated with Claude Code

Chris Smith and others added 10 commits April 23, 2026 12:48
…nalProperties

Adds sample_openapi/coverage-gaps.json, a new test_output::test_coverage_gaps
golden test, and a test_specific::test_coverage_gaps_compiles include!() test
that ensures the generated output actually compiles.

The fixture exercises schema features that were thinly covered elsewhere:
- anyOf (StringOrInt)
- oneOf with discriminator (Animal / Dog / Cat)
- additionalProperties with a typed schema (StringMap)
- additionalProperties: true / free-form (FreeformMap)

Establishing this baseline so that regressions in the upcoming openapiv3 ->
oas3 migration surface as concrete test failures rather than silent drift.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- openapi version bumped from 3.0.x to 3.1.0
- nullable: true combined with a scalar type rewritten as type: ["X", "null"]
- nullable: true alongside composed schemas (allOf/oneOf) wrapped in
  anyOf: [<original>, {type: "null"}]
- exclusiveMinimum/exclusiveMaximum boolean form already absent

This commit on its own leaves the build broken because the Rust code still
targets openapiv3; the crate swap follows immediately.
Crate-level dep changes only; code still references openapiv3 and will not
build until the subsequent migration commits.

openapiv3 remains as a transitive dep of dropshot (dev-only).
- ReferenceOrExt::item now delegates to ObjectOrReference::resolve and takes
  &Spec (oas3 resolves from the top-level spec, not just Components)
- Returns owned T (oas3 clones on resolve) instead of &T
- ComponentLookup trait is obsolete; oas3 ships FromRef for the concrete
  component types
- parameter_map now collects into BTreeMap<String, Parameter>; oas3's
  Parameter is a flat struct so param.name is a direct field
Key transforms:
- openapiv3::Parameter enum-with-per-variant-style-types → oas3 flat
  Parameter struct. Location/style now read via the `location` and `style`
  fields; missing `style` falls back to the per-location default
  (Path/Header: Simple, Query/Cookie: Form)
- parameter.schema is now a direct Option field; content lives alongside it.
  Unsupported `content`-only parameters produce an UnexpectedFormat error
- operation.responses is a single Option<BTreeMap<String, _>> keyed by the
  raw status string; a new parse_response_status helper maps "default",
  "2XX"-style ranges, and numeric codes to OperationResponseStatus
- Response.description is now Option<String>; description handling adjusted
- BTreeMap has no .first(); swapped to iter().next()/is_empty()
- oas3 strips the "x-" prefix from extension keys, so x-dropshot-pagination
  and x-dropshot-websocket are now looked up without the prefix
- ParameterDataExt is obsolete (Parameter.schema is a direct field) and has
  been replaced by a small is_simple_string_schema helper that validates
  octet-stream and plain-text body schemas against a flat ObjectSchema

to_schema.rs still references openapiv3 types, so progenitor-impl does not
build after this commit; the remaining schema rewrite lands in the next
commit.
oas3 0.21 represents schemas as a flat ObjectSchema rather than the
tagged SchemaKind enum. This rewrites the schemars conversion around
that shape:

- Split `Option<SchemaTypeSet>` into `(non_null_type, nullable)` so the
  3.1 `type: ["X", "null"]` array is normalized to a 3.0-style
  `nullable` flag for downstream schemars consumption.
- Mirror the old per-variant branches via primary-type matching, plus
  catch-all composition (one_of / all_of / any_of) branches.
- Convert BooleanSchema(bool) to schemars::schema::Schema::Bool.
- Walk exclusiveMinimum / exclusiveMaximum as serde_json::Number.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace every remaining `openapiv3::OpenAPI` reference with
`oas3::Spec`, and rewrite the path/operation walk around oas3's flat
`PathItem` shape:

- `progenitor-impl/src/lib.rs`: generate_tokens + validate_openapi +
  tag-separate builder now take `&Spec`; path walk uses
  `PathItem::methods()` (returning `http::Method`), converted to the
  lowercase string that HttpMethod::from_str expects. The version
  validator accepts both 3.0.x and 3.1.x: progenitor targets 3.1, but
  we keep 3.0 support because Dropshot emits 3.0.3 in
  `test_specific`. `nullable: true` from 3.0 is silently dropped by
  oas3.
- `progenitor-impl/src/cli.rs` and `src/httpmock.rs`: identical path
  walk + signature changes.
- `progenitor-macro/src/lib.rs` and `cargo-progenitor/src/main.rs`:
  parse into `Spec` instead of `OpenAPI`.
- `progenitor-impl/tests/test_output.rs` and `tests/test_specific.rs`:
  parse into `Spec`.

The inner generation changes (Parameter / ObjectSchema shape) landed
in earlier commits; this commit wires up the call sites so the
workspace compiles end-to-end. Goldens are regenerated in the next
commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Goldens under progenitor-impl/tests/output/src/ regenerated via
`EXPECTORATE=overwrite cargo test -p progenitor-impl`. Diffs fall into
a few categories:

- oas3's Components use BTreeMap rather than IndexMap, so components
  reorder alphabetically. Most churn is reordered impl blocks / type
  definitions in generated code.
- Operation reordering within a path: oas3's `PathItem::methods()`
  returns operations in a fixed GET/PUT/POST/DELETE/OPTIONS/HEAD/PATCH
  /TRACE order rather than spec-file order. This shifts method order
  inside `impl Client` blocks.
- 3.1 schema shapes: `type: ["string", "null"]` instead of `type:
  string` + `nullable: true` preserves identical Option<T> generation
  where fixtures were converted; a few dropshot-produced specs
  (test_stream_pagination, test_default_params) still carry 3.0
  `nullable: true` which oas3 drops, so those Option wrappers
  disappear.

Also drops the scratch MIGRATION_NOTES.md and the conversion script
used to migrate fixtures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Header parameters with nullable schemas (`type: ["string", "null"]`)
were generating field types like `Result<Option<Option<String>>, String>`
because the `required = false` wrapping in builder generation was being
applied on top of an already-optional type. Query parameters had logic
to detect this and unwrap the inner type; ports the same fix to the
Header branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Downstream consumers (e.g., yttrium) are still on reqwest 0.12; pin the
workspace to match to avoid pulling two reqwest versions into the same
dependency graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	Cargo.lock
#	Cargo.toml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant