Repository navigation
Conversation
…asfe#250) Inputs keep schemars' Deserialize contract; OperationOutput impls use a second generator with the Serialize contract. Components whose schema is the same under both contracts are shared; those that differ (directly or through a referenced component) become {Name}-Input and {Name}-Output. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4BPYPoWhLCi3UBiwPRuYi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GenContexthas oneSchemaGenerator(generate.rs,schemafield), built withschemars' default contract,
Deserialize.OperationOutputimpls such asaxum::Json<T>(
axum/outputs.rs) use that same generator, so response schemas say what serde accepts,not what it produces. Example:
Option<T>withoutskip_serializing_ifis always presentin a response, but the response schema marks it optional. TypeScript generated from the
document then has
field?: T | nullwhere the right type isfield: T | null.#254 tried two generators and stalled on the question raised in review: what happens when a
type appears on both sides? Both generators emit
#/components/schemas/Foo, and a plainextendlets one silently overwrite the other.Fix
GenContextgets a second generator.schema(inputs) is now explicitlyfor_deserialize(). The newoutput_schemaisfor_serialize().axum::Jsonandaxum::Formoutputs useoutput_schema, with matchingresolve_output_schemaandapply_output_transformsmethods.$refs under a private path(
#/components/x-aide-output-schemas/), so input and output references can always betold apart, including references nested inside other components.
ApiRouter::finish_api*),take_merged_definitionsmergesthe two sets of definitions:
This runs to a fixed point, because
Outer { inner: Rating }has the same text underboth contracts but means different things once
Ratingis split.{Name}-Inputand{Name}-Output. FastAPI/Pydantic do the same withseparate_input_output_schemas.$refinpaths,webhooksandcomponentsis rewritten to match. If a namestill collides, generation panics with a clear message instead of overwriting silently.
The result does not depend on route registration order, because both generators are kept
until the end.
Breaking for 0.16: response components gain
requiredentries, and types used on bothsides with differing schemas get new component names. Users who replace
ctx.schemawithcustom settings should now also set
ctx.output_schema.Tests
crates/aide/src/axum/contract_tests.rs(featureaxum-json+axum-query):response_option_without_skip_is_requiredresponse_option_with_skip_stays_optionalrequest_option_stays_optionalquery_option_stays_optionaltype_on_both_sides_is_split_and_refs_follow_the_side: covers the split, the transitivesplit, and a shared component that is not split
every_ref_resolves_and_no_private_path_leaksVerification
main, with only the test file added, 2 of the 6 tests fail(
response_option_without_skip_is_required,type_on_both_sides_is_split...).cargo testat the workspace root (CI's command) passes 19 lib and 18 doc tests.cargo test -p aide --all-featurespasses 27 lib and 18 doc tests.cargo fmt --checkis clean.cargo clippy -p aide --all-features --all-targetsgives the same 9 warnings asmain.cargo test -p aidewith default features does not compile onmaineither, because theoperation.rstests needmacros.Fixes #250. This builds on the two-generator idea in #254 and answers the review question
there about types used on both sides.