Skip to content

fix(binder): make the structural error codes configurable - #179

Merged
Reefact merged 4 commits into
mainfrom
147-configurable-binder-codes
Jul 18, 2026
Merged

fix(binder): make the structural error codes configurable#179
Reefact merged 4 commits into
mainfrom
147-configurable-binder-codes

Conversation

@Reefact

@Reefact Reefact commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

The binder's two structural error codes — REQUEST_ARGUMENT_REQUIRED and REQUEST_ARGUMENT_INVALID — were private with internal factories, so RequestBindingError exposed no member a consumer could reference: a consumer could only branch on a string literal, and an API that prefixes its codes was stuck with two foreign ones the binder imposed. This carries the two codes on RequestBinderOptions (defaulting to the current values, exposed publicly), so a consumer overrides them once at the entry point; every structural failure uses the configured code, and the factories stay internal.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation

Changes

  • Parameterize the two RequestBindingError factories by an ErrorCode, and expose the defaults publicly as RequestBindingError.DefaultArgumentRequiredCode / DefaultArgumentInvalidCode (so a zero-config consumer branches symbolically). The factories stay internal — a consumer recognises these errors, it does not manufacture them.
  • Carry the two codes on RequestBinderOptions (two optional constructor parameters + ArgumentRequiredCode / ArgumentInvalidCode properties), defaulting to the exposed defaults.
  • Source the configured code from the options everywhere a structural failure is raised: binder helpers RecordArgumentRequired / RecordArgumentInvalid, and NestedFailure.Group. Every path — scalars, list elements, and nested binders (which inherit the options) — uses it.
  • Add StructuralCodeOverrideTests (8 tests): custom codes flow through missing/invalid scalars, list elements, a missing complex property, and a nested binder (both codes, prefixed paths); defaults unchanged; public defaults exposed; symbolic branching.
  • Document overriding the codes and symbolic branching in the RequestBinder guide (EN + FR), and add ADR-0016 (EN + FR) recording the decision.

Testing

  • dotnet build FirstClassErrors.sln — succeeded, 0 warnings / 0 errors.
  • dotnet test FirstClassErrors.sln — all green (991 total, 0 failed), including the merged-in RequestBinder.PropertyTests list-binding invariants which independently exercise the changed converters.
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests) — 85 passed, and the dogfooded analyzers accept the parameterized factories.

GenDoc's catalog still documents the default REQUEST_ARGUMENT_* codes (the documented example lambdas produce the defaults). The branch is merged up to date with main.

Documentation

  • Public API / error documentation updated — XML docs on the new options members and the public default codes.
  • README / doc/ updated — RequestBinder guide (EN), new "Structural error codes" section.
  • French translation updated — the RequestBinder guide (RequestBinder.fr.md) and ADR-0016 (.fr.md), in lockstep.
  • No documentation change required

Architecture decisions

  • No architectural decision in this pull request
  • New decision recorded — ADR drafted as Proposed: ADR-0016
  • Supersedes an existing ADR — successor proposed, status not flipped: ADR-____
  • ⚠️ Conflicts with an existing ADR — flagged for the maintainer: ADR-____

ADR-0016 records the public-API-contract decision (the two structural codes carried on RequestBinderOptions). Drafted as Proposed; the maintainer decides its status. It was renumbered during merges with main (which took 0014 for the required-list contract #177, and 0015 for the Combine arity ceiling #178). The documentation half — surfacing an overridden code in a consumer's generated catalog without drifting from runtime — is deferred (relates to #140).

Related issues

Closes #147

🤖 Generated with Claude Code

claude added 4 commits July 18, 2026 10:48
RequestBindingError exposed no referenceable member: its two codes
(REQUEST_ARGUMENT_REQUIRED / REQUEST_ARGUMENT_INVALID) were private and
its factories internal, so a consumer could only branch on a string
literal, and an API that prefixes its codes was stuck with two foreign
ones the binder imposed.

Carry the two codes on RequestBinderOptions (defaulting to the current
values, exposed publicly as RequestBindingError.Default*Code) so a
consumer overrides them once at the entry point; the binder helpers and
NestedFailure source the configured code, so every structural failure --
scalars, list elements, and nested binders (which inherit the options) --
uses it. Factories stay internal: a consumer recognises these errors, it
does not manufacture them.

Refs: #147
Record the public-API-contract decision behind #147: the binder's two
structural error codes are carried on RequestBinderOptions, defaulting to
the current codes (exposed publicly), so a consumer overrides them once.
Drafted as Proposed and indexed (EN + FR); the maintainer decides its
status. The catalog-drift half is deferred (relates to #140).

Refs: #147
Renumber ADR-0014 -> ADR-0015 (main took 0014 for the required-list
presence contract, #177) and resolve the ADR index.
Renumber ADR-0015 -> ADR-0016 (main took 0015 for the Combine arity
ceiling, #178) and resolve the ADR index.
@Reefact
Reefact enabled auto-merge July 18, 2026 11:22
@Reefact
Reefact merged commit 0fd50ba into main Jul 18, 2026
13 checks passed
@Reefact
Reefact deleted the 147-configurable-binder-codes branch July 18, 2026 11:24
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.

Binder: RequestBindingError has no public members, forcing consumers to magic strings

2 participants