Skip to content

fix(#226): heal Swashbuckle reserved-id state in the Issue #180 cleanup (net10.0)#227

Merged
avgalex merged 2 commits into
masterfrom
feature/issue-226-state-healing-cleanup
Jul 12, 2026
Merged

fix(#226): heal Swashbuckle reserved-id state in the Issue #180 cleanup (net10.0)#227
avgalex merged 2 commits into
masterfrom
feature/issue-226-state-healing-cleanup

Conversation

@avgalex

@avgalex avgalex commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #226: when the same DTO is bound as a flattened [FromQuery]/[AsParameters] container by one endpoint and as a request body ([FromBody]/[FromForm]) by another, the Issue #180 cleanup left the type reserved-but-removed in Swashbuckle's SchemaRepository — the body operation then emitted a $ref to a component that no longer exists (an invalid document) and the FluentValidation rules never reached it. Reproduces with the default RemoveUnusedQuerySchemas = true.

The dangling-$ref mechanism was confirmed empirically: the new Phase 1 repro tests fail pre-fix with SchemaRepository.Schemas empty while the type stays reserved.

Fix (net10.0 target only)

The Issue #180 cleanup now heals the repository state using the public SchemaRepository.ReplaceSchemaId API (Swashbuckle 10.1.0+, domaindrivendev/Swashbuckle.AspNetCore#3708): the reservation is cleared together with the component removal, so the next operation binding the same type regenerates a full component and the rules reach the real document object.

Design record

docs/adr/ADR-006-shared-dto-state-healing-cleanup.md — options considered (incl. rejected reflection-based healing for net8/9 and the document-filter promotion), version matrix, consequences. Review artifacts in docs/reviews/.

Thanks to @jgarciadelanoceda for suggesting the ReplaceSchemaId approach in the PR #224 discussion.

Tests

Release

version.props7.1.11, CHANGELOG entry added.

🤖 Generated with Claude Code

…up (net10.0)

When a DTO is shared between a flattened [FromQuery]/[AsParameters] binding
and a request body ([FromBody]/[FromForm]) in the same document, the Issue
#180 cleanup left the type reserved-but-removed in SchemaRepository: a later
body operation emitted a $ref to a component that no longer exists and the
FluentValidation rules never reached the document (confirmed empirically by
the new Phase 1 repro tests).

On the net10.0 target (Swashbuckle 10.x) the cleanup now clears the internal
reserved-id via the public SchemaRepository.ReplaceSchemaId API (10.1.0+)
before removing a side-effect schema, so the repository stays consistent and
the next operation binding the same type regenerates a full component.
SwashbuckleSchemaProvider tracks Type -> schemaId for every GetSchemaForType
call (including the Issue #209 ancestor walk) to make the healing possible.

net8.0/net9.0 pin Swashbuckle 8.1.1 (no ReplaceSchemaId) and keep the 7.1.10
throwaway-recovery behavior; the fallback stays as a safety net everywhere.

Tests: new SharedDtoMixedBindingTests (JSON body + form body) and an
OPENAPI_V2 port of the #223 shared-DTO scenario — the first operation-filter
coverage on net10.0. Full matrix green: net8.0 128, net9.0 128, net10.0 99.

Design record: docs/adr/ADR-006-shared-dto-state-healing-cleanup.md
(authored and validated via the /adr-autonomous pipeline, 2 iterations).

Suggested-by: @jgarciadelanoceda (PR #224 discussion)
Issue: #226

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@avgalex

avgalex commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@claude review this PR

@claude

claude Bot commented Jul 12, 2026

Copy link
Copy Markdown

Claude finished @avgalex's task in 5m 36s —— View job


Review of PR #227

  • Gather context (CLAUDE.md, diff, changed files)
  • Review core fix: SwashbuckleSchemaProvider.cs
  • Review core fix: FluentValidationOperationFilter.cs
  • Review tests: SharedDtoMixedBindingTests.cs, SharedDtoAcrossEndpointsTest.cs
  • Review ADR/docs and version bump
  • Post review feedback

Summary

This is a solid, well-scoped fix. The root-cause analysis (Swashbuckle's SchemaRepository keeps a private "reserved-ids" map that the #180/#223 cleanup couldn't reach with public API, leaving a type "reserved-but-removed" and causing a dangling $ref) is correctly diagnosed and the new ReplaceSchemaId-based healing in FluentValidationOperationFilter.cs:262-278 addresses it directly rather than papering over symptoms.

Strengths:

  • SwashbuckleSchemaProvider.cs:26,59 tracks Type → schemaId for every GetSchemaForType call site (parameters loop + the Required property in optional nested type in [FromQuery] parameter is wrongly marked as required #209 ancestor-requiredness walk), so the healing covers all schemas the cleanup might remove — this was correctly called out as a gap risk and closed (see ADR-006-review.md iteration 2, item 2).
  • The temp-GUID-rename-then-remove pattern (FluentValidationOperationFilter.cs:270-277) is safe: ReplaceSchemaId is only invoked for ids that were just observed in context.SchemaRepository.Schemas.Keys, and it correctly falls back to the plain Schemas.Remove(schemaId) path when the type isn't tracked (nested/transitive components) or the call returns false.
  • SharedDtoMixedBindingTests.cs is genuinely convincing: it drives the real SchemaGenerator/SchemaRepository (not mocks) through a query op then a body op, and the JSON-body test (lines 56-57) calls schemaGenerator.GenerateSchema directly — exactly the code path Swashbuckle itself uses — so the assertion that the $ref resolves to a real, rule-carrying component is a strong empirical check of the fix, not just a unit-level assumption.
  • Scope is appropriately conservative: net10.0-only given the ReplaceSchemaId API only exists in Swashbuckle 10.1.0+, with net8.0/net9.0 explicitly kept on the prior fix(#223): apply FluentValidation rules to every endpoint sharing a [FromQuery] DTO #224 throwaway-recovery behavior rather than reaching for a fragile reflection-based fix (correctly rejected in ADR-006 Option B). No public API surface change; version bump (7.1.11) and CHANGELOG are consistent with a patch-level bug fix.
  • CI is green (net8.0 128/128, net9.0 128/128, net10.0 99/99 per the PR description; workflow run confirms success).

Nits (non-blocking):

  • docs/reviews/ADR-006-review.md:47 — the "Recommendation" line mixes Russian into an otherwise English document (**Approve с правками** (правки применены...)). Likely a leftover from the review-generation process; worth cleaning up for consistency with the rest of the repo's English-only docs.
  • The reverse lookup schemaProvider.RequestedSchemaIds.FirstOrDefault(pair => pair.Value == schemaId).Key (FluentValidationOperationFilter.cs:267-268) is O(n) per removed schema id, but N is always small (schemas created in a single operation), so this is a non-issue in practice — just flagging for awareness, not requesting a change.

No correctness issues found. The ADR (docs/adr/ADR-006-shared-dto-state-healing-cleanup.md) is thorough and honestly documents the remaining theoretical gap (transitively-created nested components can still leave stale reservations on all TFMs) as a known, unaddressed edge case rather than overclaiming completeness — good practice.

…view nit)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@avgalex avgalex merged commit 92c1962 into master Jul 12, 2026
2 checks passed
@avgalex avgalex deleted the feature/issue-226-state-healing-cleanup branch July 12, 2026 21:03
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.

Follow-up (#223): body-bound shared DTO ([FromBody]/[FromForm]) may not receive rules when the same type is flattened as [FromQuery]

1 participant