Skip to content

WithDefault + cyclic-build guard (2.1.0)#1

Open
jlamfers2 wants to merge 8 commits into
mainfrom
v2.1/develop
Open

WithDefault + cyclic-build guard (2.1.0)#1
jlamfers2 wants to merge 8 commits into
mainfrom
v2.1/develop

Conversation

@jlamfers2

Copy link
Copy Markdown
Owner

Summary

  • WithDefault<TValue>(x => x.Member) — the strongly-typed equivalent of the "default()" string token for a nested model member: fills the member via that type's own default builder (its SetDefaults runs). Composing it in each builder's SetDefaults lets an object graph fill itself, one explicit level per builder — no framework-wide "build deep" magic. It is to default() what WithBuilder is to a named-builder reference.
  • WithBuilder constraint relaxed classclass? — same rationale, so both typed reference-member helpers no longer warn on nullable-annotated members (e.g. Address?). Backward-compatible (a relaxation).
  • Cyclic-build guard — a thread-local reentrancy guard in ModelBuilder.Build(). Because every nested build funnels through a builder's Build(), a model type that (transitively) builds itself — via WithDefault, WithBuilder, a "default()" or named-builder value, an auto-vivified nested path, or a built list element — now throws a clear InvalidOperationException (with the cycle path) instead of an unrecoverable StackOverflowException. Not a false-positive risk: declarative builders mean a re-entered type is genuinely infinite. Sequential same-type builds (BuildMany) and independent same-type siblings are unaffected.
  • Version bump to 2.1.0 (new public API → minor per semver).

Tests

  • WithDefaultTests — ad-hoc, in SetDefaults, ctor routing, "default()" equivalence, stringnull, deep composition.
  • BuildRecursionGuardTests — mutual (Vader→Kind→Vader) and self cycles across all triggering mechanisms (WithDefault, WithBuilder, default() token, named-builder reference), same-typed siblings are not a cycle, post-throw recovery, and BuildMany (no false cycle across sequential builds; cyclic type throws).

All unit-test projects green (core 316, Fakers 24, Reqnroll 13, SpecFlow 12); solution builds with 0 warnings.

Docs

README.md + README-nl.md updated: chapter 6h (WithDefault), chapter 15 (cyclic-build guard), chapter 16 (Core file table), chapter 17 signatures.

Note for reviewers

Adding WithDefault to the public IModelBuilder<TModel>/IModelBuilder interfaces is treated as a minor bump (only ModelBuilder<,> implements them; they are not consumer-implemented contracts). If you consider those interfaces externally implementable, this would instead warrant a major bump.

🤖 Generated with Claude Code

WithDefault<TValue>(x => x.Member) is the strongly-typed equivalent of the
"default()" string token for a nested model member: it fills the member via
that type's own default builder. Composing it in each builder's SetDefaults
lets an object graph fill itself, one explicit level per builder. Relax the
WithBuilder constraint from `class` to `class?` for the same reason, so both
typed reference-member helpers no longer warn on nullable-annotated members.

Add a thread-local reentrancy guard in ModelBuilder.Build(): because every
nested build funnels through a builder's Build(), a model type that
(transitively) builds itself - via WithDefault, WithBuilder, a "default()" or
named-builder value, an auto-vivified nested path, or a built list element -
now throws a clear InvalidOperationException with the cycle path instead of an
unrecoverable StackOverflowException. Sequential same-type builds (BuildMany)
and independent same-type siblings are not affected.

Tests cover WithDefault (ad-hoc, in SetDefaults, ctor routing, "default()"
equivalence, string->null, deep composition) and the guard (mutual and self
cycles across all triggering mechanisms, sibling non-cycle, post-throw
recovery, BuildMany). Docs (README.md/README-nl.md) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

Jaap Lamfers and others added 7 commits July 8, 2026 23:28
Add the published dev.to introduction article under docs/, and link to it
from the top of README.md and README-nl.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New dependency-free faker package for Netherlands-specific test data,
following the same IFaker namespace convention as XFaker/Bogus: tokens
under `nl.*` and the `xprovider.Nl()` accessor.

Generators (valid checks where applicable):
- Bsn, Rsin, BtwNummer, Bankrekeningnummer -> elfproef (11-test)
- Iban -> ISO 13616 mod-97
- KvkNummer, Vestigingsnummer, AgbCode
- Postcode, Kenteken, Mobiel, VastTelefoonnummer
- Paspoortnummer, Rijbewijsnummer, Provincie, Gemeente

Gemeente() uses the full current CBS/Wikipedia list of all 342 Dutch
municipalities (peildatum 1-1-2024). Registers its own seeded Random
(like the Bogus integration) so its seed stays independent from XFaker.

Includes 16 unit tests (checksums, formats, determinism, coexistence
with XFaker, token resolution), README EN/NL chapter 21.4, and the
CLAUDE.md solution-layout row. CI/packaging need no change: publish.yml
packs the whole solution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aligns the recursion-guard test models/builders with the repository
convention that code, comments and identifiers are in English.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DutchFakerApi.Kenteken() generates the real Dutch sidecode XXX-99-X
(LLL-DD-L), but the test's allowed-pattern regex omitted it, so CI hit
plates like "XVX-62-T" and failed. Add the missing alternative.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follows the .NET naming guideline that two-letter acronyms are all
uppercase (like System.IO). Renames the namespace property DutchFaker.Nl
and the provider accessor xprovider.Nl() to NL, plus the README refs.
Tokens stay lowercase and case-insensitive (nl.*), so token usage is
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Core (XModelBuilder): two public, country-agnostic helpers so any IFaker
can generate valid-checksum identifiers in one line:
- RandomExtensions: Digits, PickFrom, FromPattern ('#'=digit, '?'=letter)
- Checksums: Mod11WeightedSum/IsValid (elfproef family, ISBN-10, ...),
  Luhn (cards/IMEI), Gs1 (EAN/GTIN/SSCC), Mod97 (IBAN)

DutchFakerApi now builds on these helpers (thin wrappers) and adds:
- Bic, EanCode (GS1-valid), BigNummer, UzoviCode, Geslacht

The refactor preserves the RNG draw order, so existing seeded values are
unchanged. Adds ChecksumsTests + RandomExtensionsTests (core) and tests
for the new NL methods; README EN/NL chapter 21.4 + file tables updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants