Skip to content

feat(ADR-007): promote FluentValidationDocumentFilter to a supported opt-in pipeline (UseDocumentFilter, v7.2.0)#228

Merged
avgalex merged 5 commits into
masterfrom
feature/adr-007-document-filter-promotion
Jul 12, 2026
Merged

feat(ADR-007): promote FluentValidationDocumentFilter to a supported opt-in pipeline (UseDocumentFilter, v7.2.0)#228
avgalex merged 5 commits into
masterfrom
feature/adr-007-document-filter-promotion

Conversation

@avgalex

@avgalex avgalex commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Implements ADR-007: the experimental FluentValidationDocumentFilter is promoted to a supported opt-in pipeline at full functional parity with the default schema + operation filter pipeline.

services.AddFluentValidationRulesToSwagger(
    configureRegistration: options => options.UseDocumentFilter = true);

The document filter processes the whole document at once and performs the Issue #180 cleanup once at the end, so the per-operation shared-DTO state bug class (#223/#226) cannot occur in this pipeline — on all target frameworks, including net8.0/net9.0 where the 7.1.11 ReplaceSchemaId healing API does not exist.

What's inside

Parity (previously missing in the document filter):

No-drift architecture: the #209 and #216 logic is extracted into internal components (ParameterRequiredResolver, RequestBodyRuleApplicator) shared by both pipelines. The operation filter refactor is semantics-preserving — all pre-existing tests pass unchanged.

Robustness fixes (long-standing rot): top-level try/catch (a throwing validator no longer kills document generation), ServiceProviderValidatorRegistry fallback, injected IFluentValidationRuleProvider honored, dead code removed, logger category fixed, record nullability, [Obsolete("Есть повтор")] leftover resolved.

Public API (additive, minor): RegistrationOptions.UseDocumentFilter (default false — the default pipeline is unchanged); ExperimentalUseDocumentFilter kept as an [Obsolete] forwarding alias; new optional ctor parameter on FluentValidationDocumentFilter (appended last — source-compatible). A future major version may switch the default (separate ADR).

Tests

Design process

The change was designed through an ADR process (4 review iterations) and implemented phase-by-phase with per-phase test gates; the design/review artifacts are maintained outside the repository.

Release

version.props7.2.0 (minor: additive API, default behavior unchanged), CHANGELOG entry added.

🤖 Generated with Claude Code

Behavior note (from review)

Constructing FluentValidationDocumentFilter with neither validatorRegistry nor serviceProvider now throws ArgumentNullException (matching the sibling filters) instead of silently producing a filter that applies no rules. The DI registration path (AddFluentValidationRulesToSwagger) is unaffected.

…opt-in pipeline (UseDocumentFilter)

The document filter processes the whole document at once and performs the
Issue #180 cleanup once at the end, so per-operation shared-DTO state issues
(#223/#226) cannot occur in this pipeline — on ALL target frameworks,
including net8.0/net9.0 where the 7.1.11 ReplaceSchemaId healing API does
not exist.

Parity with the default schema + operation filter pipeline:
- required-marking with the whole-dot-path check (#209)
- request bodies and encoding.contentType for [FromForm] (#216)
- multi-validator support, allOf/oneOf/anyOf traversal
- $ref preservation for unmodified properties (#198, net10.0)
- every operation of a multi-verb path is processed (was: first only)
- reserved-id healing in the cleanup on net10.0 (#226 mechanism)

The #209 and #216 logic is extracted into internal components
(ParameterRequiredResolver, RequestBodyRuleApplicator) shared by both
pipelines so they cannot drift. Robustness: top-level try/catch,
ServiceProviderValidatorRegistry fallback, injected rule provider honored
(new optional ctor parameter, appended last — source-compatible), dead code
and logging/nullability cleanups.

Public API (additive, minor): RegistrationOptions.UseDocumentFilter;
ExperimentalUseDocumentFilter kept as an [Obsolete] forwarding alias.
Default pipeline unchanged; a future major may switch the default.

Tests: new DocumentFilterParityTests (8 on OpenApi v1, 6 on OPENAPI_V2,
incl. an investigation test pinning that enum schemas ARE emitted as
$refs). Full matrix green: net8.0 136, net9.0 136, net10.0 105.
Verified live: samples/MinimalApi switched to the document-filter pipeline;
swagger.json confirms #180/#209/#216 and the #226 mixed body+query shape.

Release: version.props 7.2.0 + CHANGELOG entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@avgalex avgalex force-pushed the feature/adr-007-document-filter-promotion branch from 4a2f312 to ef28a11 Compare July 12, 2026 20:13
avgalex and others added 3 commits July 12, 2026 23:17
Removes the ADR-006/ADR-007 design and review documents from git (they are
maintained locally by the maintainer) and replaces in-code ADR references
with issue references so nothing points at files that are not in the repo.
No code changes — comments, CHANGELOG and README wording only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…opt-in pipeline (UseDocumentFilter)

The document filter processes the whole document at once and performs the
Issue #180 cleanup once at the end, so per-operation shared-DTO state issues
(#223/#226) cannot occur in this pipeline — on ALL target frameworks,
including net8.0/net9.0 where the 7.1.11 ReplaceSchemaId healing API does
not exist.

Parity with the default schema + operation filter pipeline:
- required-marking with the whole-dot-path check (#209)
- request bodies and encoding.contentType for [FromForm] (#216)
- multi-validator support, allOf/oneOf/anyOf traversal
- $ref preservation for unmodified properties (#198, net10.0)
- every operation of a multi-verb path is processed (was: first only)
- reserved-id healing in the cleanup on net10.0 (#226 mechanism)

The #209 and #216 logic is extracted into internal components
(ParameterRequiredResolver, RequestBodyRuleApplicator) shared by both
pipelines so they cannot drift. Robustness: top-level try/catch,
ServiceProviderValidatorRegistry fallback, injected rule provider honored
(new optional ctor parameter, appended last — source-compatible), dead code
and logging/nullability cleanups.

Public API (additive, minor): RegistrationOptions.UseDocumentFilter;
ExperimentalUseDocumentFilter kept as an [Obsolete] forwarding alias.
Default pipeline unchanged; a future major may switch the default.

Tests: new DocumentFilterParityTests (8 on OpenApi v1, 6 on OPENAPI_V2,
incl. an investigation test pinning that enum schemas ARE emitted as
$refs). Full matrix green: net8.0 136, net9.0 136, net10.0 105.
Verified live: samples/MinimalApi switched to the document-filter pipeline;
swagger.json confirms #180/#209/#216 and the #226 mixed body+query shape.

Release: version.props 7.2.0 + CHANGELOG entry.

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

chore: keep ADR/review process artifacts out of the repository

Removes the ADR-006/ADR-007 design and review documents from git (they are
maintained locally by the maintainer) and replaces in-code ADR references
with issue references so nothing points at files that are not in the repo.
No code changes — comments, CHANGELOG and README wording only.

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 3m 11s —— View job


Review: ADR-007 document-filter promotion (v7.2.0)

  • Gather context (PR description, changed files list)
  • Read repo guidelines (no CLAUDE.md present)
  • Review diff against origin/master
  • Review new components (ParameterRequiredResolver, RequestBodyRuleApplicator)
  • Review FluentValidationDocumentFilter changes
  • Review FluentValidationOperationFilter refactor
  • Review tests, samples, docs
  • Post final review feedback

Note: I was not able to run dotnet build/dotnet test in this environment (Bash execution requires interactive approval that isn't available here), so this is a static/code review only. The reported test matrix (net8/9 136/136, net10 105/105) should be confirmed against the CI run for this PR.

Overall

This is a well-executed refactor. Extracting the #209/#216 logic into ParameterRequiredResolver and RequestBodyRuleApplicator (shared by both pipelines) is exactly the right move to prevent drift, and the FindOperation/FindParameter fix (previously FindParam only ever looked at operations.Values.FirstOrDefault(), so multi-verb paths silently used the wrong operation's parameters) is a legitimate correctness fix beyond what's advertised. The try/catch around Apply in the document filter, matching the operation filter's existing pattern, is a good robustness addition. Public API surface change (UseDocumentFilter + obsolete-forwarding ExperimentalUseDocumentFilter) is additive and non-breaking as described.

Minor observations (non-blocking)

  1. src/MicroElements.Swashbuckle.FluentValidation/Swashbuckle/FluentValidationDocumentFilter.cs:66-68 — the constructor now does:

    _validatorRegistry = validatorRegistry ?? new ServiceProviderValidatorRegistry(
        serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)),
        schemaGenerationOptions);

    Previously _validatorRegistry could remain null and was consumed via _validatorRegistry?.GetValidator(...). Now construction throws if both validatorRegistry and serviceProvider are null. Since the filter is always built through DI (ServiceCollectionExtensions/DocumentFilterScopeAdapter/ActivatorUtilities.CreateInstance), serviceProvider is always supplied in practice, so this shouldn't affect real usage — but it is a public constructor and a behavior change worth calling out explicitly in the PR/changelog (it currently isn't).

  2. FluentValidationOperationFilter.cs:35-36,70-71_validatorRegistry is still declared IValidatorRegistry? (nullable) but the constructor's ?? fallback guarantees it's never actually null after construction (matching pre-existing behavior). Passing it into the new ParameterRequiredResolver/RequestBodyRuleApplicator constructors (which take non-nullable IValidatorRegistry) will likely produce a CS8604 nullable-warning at those two call sites under the project's <Nullable>enable</Nullable> setting. Not a functional bug (the value is genuinely non-null there) and warnings aren't treated as errors in this repo, so it won't break the build — just a small nullability-annotation nit; could tighten the field to non-nullable in a follow-up.

  3. RequestBodyRuleApplicator.cs:107 — request-body rule application still uses _validatorRegistry.GetValidator(...) (single validator), while the document filter's schema-processing loop was upgraded to GetValidators(...) (multi-validator, 7.1.2 replaces schema reference with copy of schema #198). This preserves prior operation-filter behavior and isn't a regression from this PR, but it means multi-validator support wasn't extended to [FromForm]/[FromBody] request bodies — worth a follow-up issue if that's desired for full parity with the schema pipeline.

  4. Docs/CHANGELOG/README/version.props updates are consistent with the code changes, and removing the ADR/review-process markdown files from the repo matches the stated intent (design artifacts maintained outside the repo) and the earlier "chore" commit on this branch.

Correctness spot-checks

  • The Issue Required property in optional nested type in [FromQuery] parameter is wrongly marked as required #209 whole-dot-path-required check and Issue Add support for media types #216 form-body/encoding logic are byte-for-byte extracted (not reimplemented), so no logic drift between the two pipelines.
  • The reserved-id healing block in the cleanup (FluentValidationDocumentFilter.cs:408-424) is correctly gated to OPENAPI_V2 (net10.0) only, matching the ADR-006 constraint that ReplaceSchemaId isn't available pre-Swashbuckle 10.1.0.
  • FluentValidationRules.ProcessAllSchemas was correctly changed from private to internal static — the method has no instance state, so sharing it this way is safe.
  • Nothing in the diff touches .github/workflows.

No blocking issues found. Nice work on the shared-component extraction and the multi-verb FindOperation/FindParameter fix in particular.

…avior change (PR #228 review)

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

avgalex commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review. Point-by-point:

  1. Ctor behavior change (throws when both validatorRegistry and serviceProvider are null) — agreed, now called out explicitly: added to the CHANGELOG 7.2.0 entry and to the PR description ("Behavior note"). Commit aa28764.
  2. Possible CS8604 at the new ParameterRequiredResolver/RequestBodyRuleApplicator call sites — checked with a clean build: no warning is produced. Nullable flow analysis sees the fresh non-null assignment of _validatorRegistry earlier in the same constructor, so the non-nullable parameters are satisfied. Tightening the field annotation itself is a reasonable follow-up cleanup, but there is nothing to suppress today.
  3. Single GetValidator in the request-body applicator — deliberate: the applicator is a byte-for-byte extraction of the operation filter's logic, and extending multi-validator support to request bodies would change the default pipeline's behavior too, which is out of this PR's parity scope. Happy to file a follow-up issue for multi-validator request bodies across both pipelines.
  4. Docs/versioning consistency — 👍; the ADR/review markdown files are intentionally maintained outside the repository.

Test matrix from the PR description is confirmed by this PR's CI run (both jobs green).

@avgalex avgalex merged commit b83b7cc into master Jul 12, 2026
2 checks passed
@avgalex avgalex deleted the feature/adr-007-document-filter-promotion 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.

1 participant