feat(binder)!: build a command from attached input sources - #196
Merged
Conversation
Restructure the request binder so it builds a command or query from independently attached sources rather than from a single DTO. The binder is source-agnostic and untyped: it is entered from its failure envelope — Bind.Request(envelope) — and a DTO's properties (PropertiesOf(dto)) and individually named, source-tagged out-of-DTO arguments (Argument(name).From(source, value) / ArgumentList, with FromRoute/FromQuery/... sugar) are attached as peers, sharing one collect-all envelope and one set of argument paths. The command type is named only at the New<TCommand>/Create<TCommand> terminal, inferred from the assembler; a single RequestBinder type serves top-level and nested binding, and a nested complex binding receives that RequestBinder plus the sub-DTO, so its result type is inferred with no explicit type argument. An argument's provenance is captured in the failure context under the new public RequestArgumentSource key (the argument-path key is public too), so an adapter can project a structured per-field response; a DTO property carries no source (its provenance is implicit). The request-binder guide (EN/FR) and the package README are rewritten to the new model, and ADR-0021 records the decision. BREAKING CHANGE: the entry point changes from Bind.PropertiesOf(dto).FailWith(env) to Bind.Request(env) + bind.PropertiesOf(dto); the property selectors move to PropertySource; the terminals New/Create carry the command type parameter; the converter types drop their TRequest type parameter; nested-binding functions take a non-generic RequestBinder and the sub-DTO. Pre-release, no external consumers. Refs: #148 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J12ghrzM1xiAKw4LETJDqr
Reefact
force-pushed
the
148-request-binder-sources
branch
from
July 19, 2026 19:28
369fd9e to
2068a4f
Compare
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.
Summary
Restructure the request binder so it builds a command or query from independently attached input sources rather than from a single DTO, and bind out-of-DTO arguments (route/query/header/…) as peers of the DTO's properties — the feature requested in #148. The binder becomes source-agnostic and untyped: it is entered from its failure envelope (
Bind.Request(envelope)), and the command type is named only at theNew/Createterminal.Type of change
Changes
Bind.Request(envelope)/Bind.WithOptions(...).Request(envelope)return a non-genericRequestBinder; a DTO is attached withbind.PropertiesOf(dto)(→PropertySource<TDto>) and out-of-DTO values withbind.Argument(name)/bind.ArgumentList(name).Argument(name).From(source, value), withFromRoute/FromQuery/FromHeader/FromBody/FromFormsugar (scalar and list), bound with the same converter surface as DTO properties.RequestArgumentSourcekey; the argument-path key is public too (RequestBindingError.ArgumentPathKey/ArgumentSourceKey). A DTO property carries no source (its provenance is implicit).New/Createnow carry the command type parameter, inferred from the assembler; a singleRequestBindertype serves top-level and nested binding; nested-binding functions take(RequestBinder, TDto)— no explicit type argument at the call site.TRequesttype parameter; value-type argument overloads (where T : struct) mirror the value-type property overloads.README.nuget.mdupdated to the new entry.Testing
dotnet build FirstClassErrors.sln— 0 warnings, 0 errorsdotnet test FirstClassErrors.sln— 1070 passed, 0 failedFirstClassErrors.Analyzers.UnitTests) — ran as part of the full-solution testDocumentation
doc/updated (request-binder guide EN/FR,README.nuget.md)RequestBinder.fr.mdwas kept in sync; the top-levelREADME.fr.mdreferences no changed behavior and needed no editArchitecture decisions
Proposed: ADR-0021 (bind out-of-DTO arguments as peers through a source-agnostic untyped entry)ADR-0021 records the entry-point change; the decisions of ADR-0007, ADR-0012 and ADR-0017 are unaffected — their illustrative
Bind.PropertiesOfsurface is now historical, left unedited per the ADR discipline (an accepted ADR is not edited in place).Related issues
Closes #148
🤖 Generated with Claude Code
https://claude.ai/code/session_01J12ghrzM1xiAKw4LETJDqr