Skip to content

fix(aide): apply schemars transforms to inlined schemas - #301

Open
abbudao wants to merge 1 commit into
tamasfe:mainfrom
abbudao:fix/apply-schemars-transforms
Open

abbudao wants to merge 1 commit into
tamasfe:mainfrom
abbudao:fix/apply-schemars-transforms

Conversation

@abbudao

@abbudao abbudao commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Supersedes #230, part of #270.

As @Diggsey made the original report and analysis, I made him co-author on the commit.

Intent

aide never applies its schemars settings to the schemas it inlines.

schemars runs the transforms from SchemaSettings in two places, root_schema_for and take_definitions(true), and aide uses neither when building an operation. It calls subschema_for::<T>() and reads the result back through GenContext::resolve_schema, which is a plain lookup. Nothing on that path transforms anything. (Diggsey's description on #230.)

You don't need custom settings to hit this. aide's own generator is built from SchemaSettings::draft07(), which already ships ReplacePrefixItems and friends, so a Query<T> over

struct Params { range: (u8, u8) }

emits prefixItems into a document that announces draft 7. Validators ignore keywords they don't know, so that parameter accepts any array at all. Definitions under #/components/schemas were always fine since merge_api_with collects them with take_definitions(true); only the inlined half was wrong.

Proposed Changes

GenContext gets resolve_schema_transformed (resolve a ref, return a transformed copy) and apply_transforms (transform in place). Every site that embeds a generated schema now uses one: parameters_from_schema, the media types for Json and both Forms, and TypedHeader.

apply_transforms skips $refs on purpose. They have to stay references, and their targets get transformed at merge time. That's also what makes one path work under both extract_schemas modes.

TypedPath had the reverse problem. It reads its description from root_schema_for, which does transform, then reused that schema for the parameters, so they'd now be transformed twice. Transform promises nothing about idempotency, so it takes parameters from subschema_for like every other caller.

Two differences from #230. Its parameters_from_schema change is here as-is. Its axum/inputs.rs change isn't: at that site the resolved schema is only read for .get("description") while the MediaType gets the untouched json_schema, so the transformed clone was discarded two lines later. I transformed the schema that actually gets embedded instead. That same shape turned up in five more places #230 missed (both Form inputs, both operation_response impls, TypedHeader), all fixed here.

Breaking Changes

None to the API, both methods are additive. Tested on both schemars 1.0.4 and 1.2.2.

`schemars` only applies the transforms from its settings inside
`root_schema_for` and `take_definitions(true)`. `aide` calls neither when
building operations: it uses `subschema_for` and reads schemas back via
`GenContext::resolve_schema`, so every schema `aide` inlines into the
document was emitted untransformed and the generator settings were
silently ignored.

This is not limited to custom settings. `aide` defaults to
`SchemaSettings::draft07`, which ships `ReplaceUnevaluatedProperties`,
`RemoveRefSiblings` and `ReplacePrefixItems`, so a query struct with a
tuple field emitted draft 2020-12 `prefixItems` into a draft 7 document.

Add `GenContext::resolve_schema_transformed` and
`GenContext::apply_transforms`, and use them wherever a resolved or
inline schema is embedded directly: `parameters_from_schema` (which
covers `Path`, `Query`, typed routing and `serde_qs`), the request body
and response media types for `Json` and both `Form` extractors, and the
header schema for `TypedHeader`.

Schema references are deliberately left alone, since the definitions they
point at are already transformed by `take_definitions(true)` when they are
merged into the document.

`TypedPath` needs care in the other direction: it reads its description
out of `root_schema_for`, which has already run the transforms, so reusing
that schema for the parameters would transform them twice. `Transform`
carries no idempotency guarantee, so it now takes the parameters from
`subschema_for` like every other caller of `parameters_from_schema`.

Supersedes tamasfe#230, which fixed `parameters_from_schema` but whose
`operation_input_json` hunk transformed a clone that was only read for its
description and then discarded, and which left the other five sites
untouched.

Co-authored-by: Diggory Blake <diggsey@googlemail.com>

@MOZGIII MOZGIII left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've glanced though this, and although I have nothing to do with this PR or project - the patch looks good to me

@jplatte

jplatte commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

I'll try to get to this soon!

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.

3 participants