Skip to content

Microsoft.Extensions.Configuration integration#35

Merged
feO2x merged 11 commits intomainfrom
34-config-validation
Apr 14, 2026
Merged

Microsoft.Extensions.Configuration integration#35
feO2x merged 11 commits intomainfrom
34-config-validation

Conversation

@feO2x
Copy link
Copy Markdown
Owner

@feO2x feO2x commented Apr 14, 2026

Closes #34

Summary

The main feature from plan 0034 was implemented successfully: Validator<TOptions> can participate in the IValidateOptions<TOptions> pipeline, named options are filtered correctly, DI registration is idempotent, and automated tests cover the core scenarios.

The remaining differences are mostly about API organization and two behavioral details where the implementation chose a leaner shape than the original plan text described.

Deviations From The Original Plan

1. Configuration integration was grouped into a dedicated sub-namespace instead of staying in the root validation namespace

Original plan:
The plan described adding PortableResultsValidateOptions<TOptions> "inside the existing Light.PortableResults.Validation project and namespace" and suggested a dedicated static extension class such as OptionsBuilderExtensions.

Implemented:
The configuration-related types were grouped under Light.PortableResults.Validation.ConfigurationIntegration:

  • PortableResultsValidateOptions<TOptions>
  • ConfigurationConstants

The ValidateWithPortableResults<TOptions, TValidator>() extension method was added to the existing Module class in the root namespace instead of a separate OptionsBuilderExtensions class.

Impact:
This is an organizational deviation, not a capability gap. The public API is still available, but the code is structured around a dedicated configuration-integration area rather than keeping every type directly in the root namespace.

2. Unnamed options are represented by an absent context item instead of storing OptionsNameKey with a null value

Original plan:
The plan explicitly specified forwarding the incoming options name via:

context.SetItem(OptionsNameKey, name)

This implies that validators could read the key for both named and unnamed options, with unnamed options represented by a stored null value.

Implemented:
PortableResultsValidateOptions<TOptions>.Validate(...) only stores the item when name is not null:

  • named options: ConfigurationConstants.OptionsNameKey is present
  • unnamed options: the key is not stored at all

This means validators must interpret unnamed options via TryGetItem(...) == false rather than via a present key whose value is null.

Impact:
This is a real behavioral deviation from the plan wording. The current implementation still lets validators distinguish named options, but it does not preserve the exact "forward the incoming null name" semantics that the plan described.

3. The adapter relies on a fresh root ValidationContext instead of explicitly passing ValidationTarget.Absolute("")

Original plan:
The bridge adapter was supposed to call:

_validator.CheckForErrors(options, context, out var failure, ValidationTarget.Absolute(""))

The plan used this explicit target to guarantee clean property paths such as ConnectionString.

Implemented:
The adapter creates a fresh root context from ValidationContextFactory and then calls:

_validator.CheckForErrors(options, context, out var failure)

No explicit ValidationTarget.Absolute("") is passed.

Impact:
With the current DefaultValidationContextFactory, this is functionally equivalent because a newly created root ValidationContext already starts with an empty target prefix. As a result, property paths remain clean today.

The deviation is architectural: the root-target behavior now depends on root-context semantics rather than being made explicit at the adapter call site.

4. The final API only supports Validator<TOptions>, not transforming Validator<TSource, TValidated>

Original plan:
The plan text is internally inconsistent:

  • the acceptance criteria state that synchronous Validator<T> and Validator<TSource, TValidated> should be supported when TSource == TOptions
  • the later scope section says transforming validators are not relevant for options validation and can be added later if needed

The git history shows why this inconsistency surfaced in the final implementation. Shortly before the configuration-validation work landed, commit acec552 (chore!: remove TryValidate from all validators, remove CheckForErrors from Validator<TSource, TValidated>) simplified the validator surface and removed the non-generic failure wrapper from transforming validators. The actual configuration-validation feature then landed in commit 3f73fae (feat: add support for IValidateOptions<T>).

Implemented:
The implementation resolved this in favor of the narrower scope:

  • PortableResultsValidateOptions<TOptions> accepts Validator<TOptions> only
  • ValidateWithPortableResults<TOptions, TValidator>() is constrained to where TValidator : Validator<TOptions>
  • there is no overload for Validator<TOptions, TValidated>

Impact:
This is the most important functional deviation from the acceptance-criteria wording. The branch supports only non-transforming synchronous validators for options validation.

Given the later scope section in the original plan and the preceding API simplification in acec552, this looks like an intentional narrowing rather than an accidental omission. In other words, the implementation did not merely skip a planned overload; it aligned the feature with the validator API that existed by the time IValidateOptions<T> support was introduced.

Notes On Items Implemented As Planned

The following parts of plan 0034 match the current implementation:

  • PortableResultsValidateOptions<TOptions> implements IValidateOptions<TOptions>
  • failures are mapped to ValidateOptionsResult.Fail(IEnumerable<string>)
  • named-options filtering returns ValidateOptionsResult.Skip
  • AddValidationForPortableResults() uses idempotent registration patterns
  • ValidateWithPortableResults<TOptions, TValidator>() returns the original OptionsBuilder<TOptions> for chaining
  • automated tests cover the adapter, registration, name filtering, skip behavior, and failure mapping

@feO2x feO2x self-assigned this Apr 14, 2026
@feO2x feO2x added the enhancement New feature or request label Apr 14, 2026
@feO2x feO2x linked an issue Apr 14, 2026 that may be closed by this pull request
7 tasks
@feO2x feO2x requested a review from Copilot April 14, 2026 05:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds first-class integration between Light.PortableResults.Validation and Microsoft.Extensions.Options so Validator<TOptions> can participate in the IValidateOptions<TOptions> pipeline when options are resolved from DI (including named options), alongside a version bump and related documentation/test updates.

Changes:

  • Add an IValidateOptions<TOptions> adapter (PortableResultsValidateOptions<TOptions>) + a fluent OptionsBuilder<TOptions>.ValidateWithPortableResults<TOptions, TValidator>() registration extension.
  • Refactor validator public APIs away from caller-expression string overloads and remove TryValidate wrappers, updating tests accordingly.
  • Update docs/release notes/versioning (0.3.0) and extend ErrorCategory with HTTP 418.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/Light.PortableResults.Validation.Tests/packages.lock.json Updates locked dependencies for validation tests (including Options).
tests/Light.PortableResults.Validation.Tests/ValidatorOverloadCoverageTests.cs Removes coverage tests for removed TryValidate / transforming wrappers.
tests/Light.PortableResults.Validation.Tests/ValidationConfigurationTests.cs Updates tests to match new validator overload shapes.
tests/Light.PortableResults.Validation.Tests/ValidateWithPortableResultsTests.cs Adds DI/Options pipeline tests for ValidateWithPortableResults.
tests/Light.PortableResults.Validation.Tests/SynchronousValidatorWorkflowTests.cs Adjusts workflow tests to use Validate instead of removed wrappers/targets.
tests/Light.PortableResults.Validation.Tests/ConfigurationIntegration/PortableResultsValidateOptionsTests.cs Adds unit tests for the adapter behavior (skip/success/fail/name forwarding).
tests/Light.PortableResults.Validation.Tests/AsyncValidatorWorkflowTests.cs Updates async workflow tests to align with new overloads/targets.
tests/Light.PortableResults.Tests/ErrorCategoryTests.cs Adds test coverage for the new HTTP 418 category.
src/Light.PortableResults/Light.PortableResults.csproj Bumps package release notes to 0.3.0 and adjusts wording.
src/Light.PortableResults/ErrorCategory.cs Expands docs for HTTP 410 and adds HTTP 418 ImATeapot.
src/Light.PortableResults.Validation/Validator.cs Removes caller-expression string overloads and TryValidate; changes default target semantics.
src/Light.PortableResults.Validation/Module.cs Makes DI registration idempotent and adds ValidateWithPortableResults extension.
src/Light.PortableResults.Validation/Light.PortableResults.Validation.csproj Updates release notes and mentions options validation integration.
src/Light.PortableResults.Validation/ConfigurationIntegration/PortableResultsValidateOptions.cs Introduces IValidateOptions<TOptions> adapter over Validator<TOptions>.
src/Light.PortableResults.Validation/ConfigurationIntegration/ConfigurationConstants.cs Introduces a well-known ValidationContextKey<string?> for options name.
src/Light.PortableResults.Validation/AsyncValidator.cs Aligns async validator overloads with new default target semantics (no caller-expression).
src/Light.PortableResults.AspNetCore.Shared/Light.PortableResults.AspNetCore.Shared.csproj Updates release notes/version to 0.3.0 and punctuation.
src/Light.PortableResults.AspNetCore.Mvc/Light.PortableResults.AspNetCore.Mvc.csproj Updates release notes/version to 0.3.0.
src/Light.PortableResults.AspNetCore.MinimalApis/Light.PortableResults.AspNetCore.MinimalApis.csproj Updates release notes/version to 0.3.0.
ai-plans/0034-plan-deviations.md Documents deviations between the implementation and plan 0034.
ai-plans/0034-configuration-validation.md Adds the original plan document for configuration/options validation.
README.md Adds documentation for options validation integration and bumps badge version.
Light.PortableResults.slnx Adds the plan documents to the solution.
Directory.Build.props Bumps repository version to 0.3.0.

Comment thread README.md
Comment thread src/Light.PortableResults/ErrorCategory.cs Outdated
Comment thread tests/Light.PortableResults.Validation.Tests/packages.lock.json Outdated
Comment thread src/Light.PortableResults.Validation/Module.cs
Comment thread src/Light.PortableResults.Validation/Validator.cs Outdated
Comment thread src/Light.PortableResults.Validation/AsyncValidator.cs
@github-actions
Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Light.PortableResults 96% 94% 2300
Light.PortableResults.AspNetCore.MinimalApis 93% 80% 25
Light.PortableResults.AspNetCore.Mvc 93% 80% 26
Light.PortableResults.AspNetCore.Shared 94% 92% 28
Light.PortableResults.Validation 98% 89% 1692
Summary 97% (6523 / 6722) 92% (2977 / 3241) 4071

Minimum allowed line rate is 60%

@feO2x feO2x merged commit b538c4a into main Apr 14, 2026
2 checks passed
@feO2x feO2x deleted the 34-config-validation branch April 14, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configuration Validation via IValidateOptions<T>

2 participants