Skip to content

Normalize JSON request-body validation error keys#106

Merged
KaliCZ merged 2 commits into
mainfrom
claude/mystifying-brahmagupta-cb31f1
Jun 24, 2026
Merged

Normalize JSON request-body validation error keys#106
KaliCZ merged 2 commits into
mainfrom
claude/mystifying-brahmagupta-cb31f1

Conversation

@KaliCZ

@KaliCZ KaliCZ commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What & why

A strong type that fails to deserialize from a JSON request body is reported by the System.Text.Json input formatter under the JSON path ($.value), whereas data-annotation and model-binding errors key by the property name (Value). This reconciles them, fixes a latent numeric-converter bug, and codifies the error-key contract in tests.

Investigation surfaced three distinct failure mechanisms (all confirmed against a running pipeline, not assumed):

Failure Raw key
Malformed non-null value (any type) $.value
Reference-type null (implicit-required) Value (PascalCase C# name)
Numeric type-mismatch / null was $ + body — a latent converter bug

Changes

  • Numeric converter fixNumericStrongTypeJsonConverterFactory re-entered Deserialize<T>, losing the property position on type-mismatch/null (key surfaced as $ + body). It now rethrows path-less so the serializer reattaches $.value, matching the string converter. Unit-tested (fails before / passes after).
  • Opt-out normalizationAddStrongTypes(o => …) rewrites $.-prefixed keys on the [ApiController] ValidationProblemDetails to the property name. NormalizeJsonErrorKeys defaults on; JsonErrorKeyCasing defaults PascalCase (matches the framework-default data-annotation keys), with CamelCase/StripOnly for camelCase apps. Scoped strictly to the MVC validation response (not raw JsonSerializer, not minimal APIs; model-binding keys untouched). Wired via PostConfigure so it is registration-order-independent.
  • Tests (both modes, no per-type duplication)EntityTests (all types) now asserts the full ValidationProblemDetails shape + the raw $.value/$.nullableValue keys (un-normalized baseline — the Api harness doesn't call AddStrongTypes). JsonBodyErrorKeyTests runs one bool normalize theory across two factory variants over a reference + struct type, pinning every mechanism in both modes, plus a capstone proving normalized keys match real [Required]/[EmailAddress] keys. Normalizer + converter unit tests lock the details.
  • Docstesting.md, Skill/SKILL.md + references/aspnetcore.md, package readme.

Caveat

Exact parity with data annotations isn't universally achievable: the body path carries the JSON wire name, validation carries the C# name, and there's no metadata at the rewrite layer — so casing is a per-segment heuristic. It nails the common flat-property case; a custom [JsonPropertyName("user_name")] can't be recovered from the path. Documented in the reference and the option's XML docs.

Verification

  • core unit tests: 1045 pass
  • StrongTypes.AspNetCore.IntegrationTests: 46 pass
  • StrongTypes.Api.IntegrationTests: 827 pass (17 SQL-Server cases skipped — ARM64 host, per the documented STRONGTYPES_SKIP_SQLSERVER opt-out)
  • full solution builds clean under TreatWarningsAsErrors

🤖 Generated with Claude Code

Strong types that fail to deserialize from a JSON body are reported by
the System.Text.Json input formatter under the JSON path ($.value),
whereas data-annotation and model-binding errors key by the property
name (Value). This adds an opt-out normalization in StrongTypes.AspNetCore
that reconciles them, fixes a numeric-converter bug that lost the path
entirely on type-mismatch/null, and codifies the error-key contract in
tests.

- NumericStrongTypeJsonConverterFactory: type-mismatch/null re-entered
  Deserialize<T> and surfaced as "$" + "body"; rethrow path-less so the
  serializer reattaches the property path ("$.value"), matching the
  string converter.
- AddStrongTypes(o => ...): opt-out NormalizeJsonErrorKeys (default on)
  rewrites $.-prefixed keys on the [ApiController] ValidationProblemDetails
  to the property name, with configurable JsonErrorKeyCasing (default
  PascalCase). Wired via PostConfigure so it is order-independent.
- Tests: EntityTests now asserts the ValidationProblemDetails structure +
  raw keys for every type (un-normalized baseline); JsonBodyErrorKeyTests
  pins both modes over a reference + struct type; normalizer + converter
  unit tests lock the details.
- Docs: testing.md, Skill (SKILL.md + aspnetcore.md), package readme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KaliCZ KaliCZ self-assigned this Jun 23, 2026
@github-actions

Copy link
Copy Markdown

Coverage

Lines: 3825 / 5176 (73.9%)    Branches: 2038 / 2736 (74.5%)

Files changed in this PR

File Lines Branches
StrongTypes/Numbers/NumericStrongTypeJsonConverterFactory.cs 42 / 42 (100.0%) 5 / 6 (83.3%)
StrongTypes.AspNetCore/JsonValidationErrorKeyNormalizer.cs 22 / 22 (100.0%) 20 / 20 (100.0%)
StrongTypes.AspNetCore/StrongTypesAspNetCoreOptions.cs 2 / 2 (100.0%) 0 / 0 (n/a)
StrongTypes.AspNetCore/StrongTypesServiceCollectionExtensions.cs 39 / 42 (92.9%) 13 / 16 (81.2%)
StrongTypes.AspNetCore.TestApi/Controllers/JsonBodyProbeController.cs 4 / 8 (50.0%) 0 / 0 (n/a)
StrongTypes — lines 93.1% (1520/1632), branches 88.2% (997/1130)
Booleans — lines 100.0% (14/14), branches 84.6% (22/26)
File Lines Branches
BooleanExtensions.cs 2 / 2 (100.0%) 2 / 2 (100.0%)
BooleanMapExtensions.cs 12 / 12 (100.0%) 20 / 24 (83.3%)
Collections — lines 90.2% (276/306), branches 85.1% (114/134)
File Lines Branches
IEnumerableExtensions.cs 7 / 7 (100.0%) 6 / 6 (100.0%)
IEnumerableExtensions_Concatenating.cs 2 / 2 (100.0%) 2 / 2 (100.0%)
IEnumerableExtensions_Flattening.cs 1 / 1 (100.0%) 2 / 2 (100.0%)
IEnumerableExtensions_Null.cs 5 / 5 (100.0%) 10 / 10 (100.0%)
IEnumerableExtensions_Partition.cs 11 / 11 (100.0%) 6 / 6 (100.0%)
IEnumerableExtensions_Types.cs 0 / 8 (0.0%) 0 / 6 (0.0%)
NonEmptyEnumerable.cs 59 / 65 (90.8%) 30 / 38 (78.9%)
NonEmptyEnumerableDebugView.cs 0 / 2 (0.0%) 0 / 0 (n/a)
NonEmptyEnumerableExtensions.cs 144 / 149 (96.6%) 37 / 38 (97.4%)
NonEmptyEnumerableJsonConverter.cs 47 / 50 (94.0%) 21 / 26 (80.8%)
ReadOnlyList.cs 0 / 6 (0.0%) 0 / 0 (n/a)
Digits — lines 97.7% (85/87), branches 93.8% (30/32)
File Lines Branches
Digit.cs 78 / 80 (97.5%) 26 / 28 (92.9%)
DigitExtensions.cs 7 / 7 (100.0%) 4 / 4 (100.0%)
Emails — lines 98.6% (68/69), branches 92.5% (37/40)
File Lines Branches
Email.cs 50 / 50 (100.0%) 27 / 30 (90.0%)
EmailExtensions.cs 10 / 11 (90.9%) 8 / 8 (100.0%)
EmailJsonConverter.cs 8 / 8 (100.0%) 2 / 2 (100.0%)
Enums — lines 100.0% (58/58), branches 95.5% (21/22)
File Lines Branches
EnumExtensions.cs 58 / 58 (100.0%) 21 / 22 (95.5%)
Exceptions — lines 78.9% (15/19), branches 50.0% (7/14)
File Lines Branches
ExceptionEnumerableExtensions.cs 15 / 19 (78.9%) 7 / 14 (50.0%)
Maybe — lines 92.1% (151/164), branches 83.9% (94/112)
File Lines Branches
Maybe.cs 58 / 59 (98.3%) 40 / 44 (90.9%)
MaybeCollectionExtensions.cs 41 / 45 (91.1%) 28 / 30 (93.3%)
MaybeExtensions.cs 9 / 14 (64.3%) 10 / 18 (55.6%)
MaybeJsonConverter.cs 43 / 46 (93.5%) 16 / 20 (80.0%)
Nullables — lines 100.0% (12/12), branches 83.3% (20/24)
File Lines Branches
NullableMapExtensions.cs 12 / 12 (100.0%) 20 / 24 (83.3%)
Numbers — lines 100.0% (84/84), branches 94.4% (17/18)
File Lines Branches
Negative.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
NonNegative.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
NonPositive.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
NumberExtensions.cs 12 / 12 (100.0%) 4 / 4 (100.0%)
NumericStrongTypeJsonConverterFactory.cs 42 / 42 (100.0%) 5 / 6 (83.3%)
NumericWrapperAttribute.cs 2 / 2 (100.0%) 0 / 0 (n/a)
Positive.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
Result — lines 98.5% (333/338), branches 95.6% (417/436)
File Lines Branches
Result.cs 68 / 70 (97.1%) 55 / 68 (80.9%)
ResultAccessExtensions.cs 4 / 4 (100.0%) 8 / 8 (100.0%)
ResultAggregate.cs 188 / 188 (100.0%) 318 / 318 (100.0%)
ResultCatch.cs 20 / 20 (100.0%) 0 / 0 (n/a)
ResultFlattenExtensions.cs 7 / 7 (100.0%) 8 / 8 (100.0%)
ResultFromNullableExtensions.cs 10 / 10 (100.0%) 15 / 20 (75.0%)
ResultPartitionExtensions.cs 20 / 20 (100.0%) 5 / 6 (83.3%)
ResultThrowIfErrorExtensions.cs 16 / 19 (84.2%) 8 / 8 (100.0%)
Strings — lines 84.7% (133/157), branches 84.4% (54/64)
File Lines Branches
NonEmptyString.cs 87 / 87 (100.0%) 32 / 32 (100.0%)
NonEmptyStringExtensions.cs 13 / 27 (48.1%) 0 / 0 (n/a)
NonEmptyStringJsonConverter.cs 12 / 15 (80.0%) 4 / 6 (66.7%)
StringExtensions.cs 21 / 28 (75.0%) 18 / 26 (69.2%)
generated — lines 89.8% (291/324), branches 78.8% (164/208)
File Lines Branches
Negative<T>.Extensions.g.cs 32 / 32 (100.0%) 20 / 20 (100.0%)
Negative<T>.g.cs 45 / 49 (91.8%) 24 / 32 (75.0%)
NonNegative<T>.Extensions.g.cs 32 / 32 (100.0%) 20 / 20 (100.0%)
NonNegative<T>.g.cs 36 / 49 (73.5%) 18 / 32 (56.2%)
NonPositive<T>.Extensions.g.cs 32 / 32 (100.0%) 20 / 20 (100.0%)
NonPositive<T>.g.cs 36 / 49 (73.5%) 18 / 32 (56.2%)
Positive<T>.Extensions.g.cs 32 / 32 (100.0%) 20 / 20 (100.0%)
Positive<T>.g.cs 46 / 49 (93.9%) 24 / 32 (75.0%)
StrongTypes.Analyzers — lines 94.7% (322/340), branches 86.8% (132/152)
(root) — lines 94.7% (322/340), branches 86.8% (132/152)
File Lines Branches
AddEfCorePackageCodeFixProvider.cs 48 / 52 (92.3%) 18 / 20 (90.0%)
AddOpenApiPackageCodeFixProvider.cs 52 / 53 (98.1%) 21 / 26 (80.8%)
MissingEfCorePackageAnalyzer.cs 140 / 153 (91.5%) 53 / 62 (85.5%)
MissingOpenApiPackageAnalyzer.cs 82 / 82 (100.0%) 40 / 44 (90.9%)
StrongTypes.Api — lines 96.7% (327/338), branches 86.3% (88/102)
(root) — lines 100.0% (11/11), branches n/a (0/0)
File Lines Branches
Program.cs 11 / 11 (100.0%) 0 / 0 (n/a)
Controllers — lines 96.9% (221/228), branches 86.3% (88/102)
File Lines Branches
BindingProbeController.cs 55 / 62 (88.7%) 32 / 34 (94.1%)
CollectionJsonController.cs 4 / 4 (100.0%) 0 / 0 (n/a)
EmailEntityController.cs 44 / 44 (100.0%) 26 / 30 (86.7%)
EntityControllerBase.cs 9 / 9 (100.0%) 0 / 0 (n/a)
NegativeDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonEmptyStringEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
ReferenceTypeEntityControllerBase.cs 42 / 42 (100.0%) 14 / 18 (77.8%)
StructTypeEntityControllerBase.cs 42 / 42 (100.0%) 16 / 20 (80.0%)
Data — lines 100.0% (54/54), branches n/a (0/0)
File Lines Branches
PostgreSqlDbContext.cs 27 / 27 (100.0%) 0 / 0 (n/a)
SqlServerDbContext.cs 27 / 27 (100.0%) 0 / 0 (n/a)
Entities — lines 75.0% (12/16), branches n/a (0/0)
File Lines Branches
EntityBase.cs 12 / 12 (100.0%) 0 / 0 (n/a)
IEntity.cs 0 / 4 (0.0%) 0 / 0 (n/a)
Models — lines 100.0% (29/29), branches n/a (0/0)
File Lines Branches
CollectionJsonModels.cs 20 / 20 (100.0%) 0 / 0 (n/a)
EntityModels.cs 9 / 9 (100.0%) 0 / 0 (n/a)
StrongTypes.AspNetCore — lines 89.3% (151/169), branches 84.5% (71/84)
(root) — lines 89.3% (151/169), branches 84.5% (71/84)
File Lines Branches
JsonValidationErrorKeyNormalizer.cs 22 / 22 (100.0%) 20 / 20 (100.0%)
ModelMetadataNullability.cs 12 / 13 (92.3%) 7 / 10 (70.0%)
NonEmptyEnumerableModelBinder.cs 51 / 62 (82.3%) 25 / 30 (83.3%)
NonEmptyEnumerableModelBinderProvider.cs 9 / 9 (100.0%) 4 / 4 (100.0%)
StringElementParser.cs 16 / 19 (84.2%) 2 / 4 (50.0%)
StrongTypesAspNetCoreOptions.cs 2 / 2 (100.0%) 0 / 0 (n/a)
StrongTypesServiceCollectionExtensions.cs 39 / 42 (92.9%) 13 / 16 (81.2%)
StrongTypes.AspNetCore.TestApi — lines 90.7% (39/43), branches 92.5% (37/40)
(root) — lines 100.0% (6/6), branches n/a (0/0)
File Lines Branches
Program.cs 6 / 6 (100.0%) 0 / 0 (n/a)
Controllers — lines 89.2% (33/37), branches 92.5% (37/40)
File Lines Branches
BindingProbeController.cs 29 / 29 (100.0%) 37 / 40 (92.5%)
JsonBodyProbeController.cs 4 / 8 (50.0%) 0 / 0 (n/a)
StrongTypes.EfCore — lines 89.3% (117/131), branches 71.4% (40/56)
(root) — lines 89.3% (117/131), branches 71.4% (40/56)
File Lines Branches
MailAddressValueConverter.cs 3 / 3 (100.0%) 0 / 0 (n/a)
NonEmptyStringValueConverter.cs 3 / 3 (100.0%) 0 / 0 (n/a)
NumericStrongTypeValueConverter.cs 17 / 17 (100.0%) 0 / 0 (n/a)
StrongTypesConvention.cs 46 / 59 (78.0%) 28 / 42 (66.7%)
StrongTypesDbContextOptionsExtension.cs 18 / 19 (94.7%) 2 / 2 (100.0%)
UnwrapMethodCallTranslator.cs 30 / 30 (100.0%) 10 / 12 (83.3%)
StrongTypes.FsCheck — lines 77.4% (96/124), branches n/a (0/0)
(root) — lines 77.4% (96/124), branches n/a (0/0)
File Lines Branches
Generators.cs 96 / 124 (77.4%) 0 / 0 (n/a)
StrongTypes.OpenApi.Core — lines 91.2% (424/465), branches 83.0% (284/342)
(root) — lines 91.7% (254/277), branches 86.2% (162/188)
File Lines Branches
NumericWrapperKinds.cs 11 / 11 (100.0%) 0 / 0 (n/a)
NumericWrapperPainter.cs 12 / 12 (100.0%) 3 / 4 (75.0%)
PrimitiveSchemaMap.cs 19 / 19 (100.0%) 0 / 0 (n/a)
SchemaPaint.cs 82 / 89 (92.1%) 67 / 82 (81.7%)
StrongTypeInlineMarker.cs 6 / 6 (100.0%) 6 / 6 (100.0%)
StrongTypeSchemaTypes.cs 42 / 42 (100.0%) 32 / 36 (88.9%)
WrapperAnnotationApplier.cs 82 / 98 (83.7%) 54 / 60 (90.0%)
Inlining — lines 90.4% (170/188), branches 79.2% (122/154)
File Lines Branches
StrongTypeInliner.cs 170 / 188 (90.4%) 122 / 154 (79.2%)
StrongTypes.OpenApi.Microsoft — lines 39.1% (425/1088), branches 40.3% (171/424)
(root) — lines 94.6% (245/259), branches 74.6% (97/130)
File Lines Branches
MicrosoftSchemaNaming.cs 52 / 52 (100.0%) 8 / 8 (100.0%)
PropertyAnnotationSchemaTransformer.cs 77 / 89 (86.5%) 52 / 74 (70.3%)
Startup.cs 14 / 14 (100.0%) 0 / 0 (n/a)
StrongTypesComponentSchemaFiller.cs 102 / 104 (98.1%) 37 / 48 (77.1%)
Binding — lines 98.1% (103/105), branches 78.6% (55/70)
File Lines Branches
NonBodyStrongTypeOperationTransformer.cs 103 / 105 (98.1%) 55 / 70 (78.6%)
Collections — lines 100.0% (19/19), branches 100.0% (8/8)
File Lines Branches
NonEmptyEnumerableSchemaTransformer.cs 10 / 10 (100.0%) 2 / 2 (100.0%)
StrongTypeCollectionShapeTransformer.cs 9 / 9 (100.0%) 6 / 6 (100.0%)
Digits — lines 100.0% (11/11), branches 100.0% (2/2)
File Lines Branches
DigitSchemaTransformer.cs 11 / 11 (100.0%) 2 / 2 (100.0%)
Emails — lines 100.0% (11/11), branches 100.0% (2/2)
File Lines Branches
EmailSchemaTransformer.cs 11 / 11 (100.0%) 2 / 2 (100.0%)
Inlining — lines 100.0% (7/7), branches 50.0% (1/2)
File Lines Branches
StrongTypeInliningDocumentTransformer.cs 7 / 7 (100.0%) 1 / 2 (50.0%)
Maybe — lines 100.0% (12/12), branches 100.0% (2/2)
File Lines Branches
MaybeSchemaTransformer.cs 12 / 12 (100.0%) 2 / 2 (100.0%)
Numbers — lines 100.0% (8/8), branches 100.0% (2/2)
File Lines Branches
NumericStrongTypeSchemaTransformer.cs 8 / 8 (100.0%) 2 / 2 (100.0%)
Strings — lines 100.0% (9/9), branches 100.0% (2/2)
File Lines Branches
NonEmptyStringSchemaTransformer.cs 9 / 9 (100.0%) 2 / 2 (100.0%)
obj/Debug/net10.0/Microsoft.AspNetCore.OpenApi.SourceGenerators/Microsoft.AspNetCore.OpenApi.SourceGenerators.XmlCommentGenerator — lines 0.0% (0/647), branches 0.0% (0/204)
File Lines Branches
OpenApiXmlCommentSupport.generated.cs 0 / 647 (0.0%) 0 / 204 (0.0%)
StrongTypes.OpenApi.Swashbuckle — lines 93.9% (230/245), branches 76.3% (151/198)
(root) — lines 98.5% (67/68), branches 90.3% (56/62)
File Lines Branches
PropertyAnnotationSchemaFilter.cs 55 / 56 (98.2%) 56 / 62 (90.3%)
Startup.cs 12 / 12 (100.0%) 0 / 0 (n/a)
Binding — lines 88.3% (106/120), branches 64.3% (72/112)
File Lines Branches
NonBodyStrongTypeOperationFilter.cs 106 / 120 (88.3%) 72 / 112 (64.3%)
Collections — lines 100.0% (6/6), branches 100.0% (4/4)
File Lines Branches
NonEmptyEnumerableSchemaFilter.cs 6 / 6 (100.0%) 4 / 4 (100.0%)
Digits — lines 100.0% (10/10), branches 100.0% (4/4)
File Lines Branches
DigitSchemaFilter.cs 10 / 10 (100.0%) 4 / 4 (100.0%)
Emails — lines 100.0% (10/10), branches 100.0% (4/4)
File Lines Branches
EmailSchemaFilter.cs 10 / 10 (100.0%) 4 / 4 (100.0%)
Inlining — lines 100.0% (2/2), branches n/a (0/0)
File Lines Branches
StrongTypeInliningDocumentFilter.cs 2 / 2 (100.0%) 0 / 0 (n/a)
Maybe — lines 100.0% (13/13), branches 75.0% (3/4)
File Lines Branches
MaybeSchemaFilter.cs 13 / 13 (100.0%) 3 / 4 (75.0%)
Numbers — lines 100.0% (8/8), branches 100.0% (4/4)
File Lines Branches
NumericStrongTypeSchemaFilter.cs 8 / 8 (100.0%) 4 / 4 (100.0%)
Strings — lines 100.0% (8/8), branches 100.0% (4/4)
File Lines Branches
NonEmptyStringSchemaFilter.cs 8 / 8 (100.0%) 4 / 4 (100.0%)
StrongTypes.OpenApi.TestApi.Microsoft — lines 41.0% (163/398), branches 31.6% (65/206)
(root) — lines 100.0% (14/14), branches 100.0% (2/2)
File Lines Branches
Program.cs 14 / 14 (100.0%) 2 / 2 (100.0%)
obj/Debug/net10.0/Microsoft.AspNetCore.OpenApi.SourceGenerators/Microsoft.AspNetCore.OpenApi.SourceGenerators.XmlCommentGenerator — lines 38.8% (149/384), branches 30.9% (63/204)
File Lines Branches
OpenApiXmlCommentSupport.generated.cs 149 / 384 (38.8%) 63 / 204 (30.9%)
StrongTypes.OpenApi.TestApi.Shared — lines 0.0% (0/192), branches n/a (0/0)
(root) — lines 0.0% (0/192), branches n/a (0/0)
File Lines Branches
AnnotatedRequests.cs 0 / 84 (0.0%) 0 / 0 (n/a)
BasicControllers.cs 0 / 59 (0.0%) 0 / 0 (n/a)
CustomAnnotationsControllers.cs 0 / 4 (0.0%) 0 / 0 (n/a)
Models.cs 0 / 45 (0.0%) 0 / 0 (n/a)
StrongTypes.OpenApi.TestApi.Swashbuckle — lines 100.0% (11/11), branches 100.0% (2/2)
(root) — lines 100.0% (11/11), branches 100.0% (2/2)
File Lines Branches
Program.cs 11 / 11 (100.0%) 2 / 2 (100.0%)

Surface AddStrongTypes(o => o.NormalizeJsonErrorKeys = ...) in the root
readme package table and the SKILL.md AspNetCore entry, so the opt-out
parameter is discoverable without opening the package reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KaliCZ KaliCZ merged commit 3278b91 into main Jun 24, 2026
2 checks passed
@KaliCZ KaliCZ deleted the claude/mystifying-brahmagupta-cb31f1 branch June 24, 2026 07:34
KaliCZ added a commit that referenced this pull request Jun 24, 2026
Reviewing PR #77 (interval strong types) on top of latest main surfaced
gaps in error reporting, integration coverage, and docs. This addresses
them.

Implementation:
- IntervalJsonConverterFactory: nested endpoint reads now rethrow path-less
  so System.Text.Json reattaches the property path. A null/type-mismatch
  endpoint was surfacing as the document root ("$") instead of "$.value" —
  the same bug #106 fixed for the numeric converter. Error messages also no
  longer leak the arity-suffixed CLR name ("ClosedInterval`1").

Unit tests (StrongTypes.Tests):
- Contains over open/closed bounds and ToString for every variant, JSON
  edge cases (missing/extra/reordered properties, non-object tokens, a
  DateOnly/DateTime endpoint), error-message quality, and the path-less
  rethrow. Added generator-branch coverage facts per testing.md.

API integration tests (StrongTypes.Api*):
- Four interval entities/controllers mapped to a single JSON column via the
  shipped HasIntervalJsonConversion. Dedicated wire-to-DB suite covering the
  round-trip on both providers, null handling, update, and invalid payloads
  keyed at "$.value" (the #106 contract).

OpenAPI (StrongTypes.OpenApi.*):
- Interval schema transformer (Microsoft) and filter (Swashbuckle) render
  each variant as { Start, End } with required: [Start, End] and per-variant
  endpoint nullability, inlined like the other wrappers. CloneWireShape now
  preserves `required`. Tested on all three pipeline configs.

Docs:
- README, SKILL.md + new references/intervals.md, and the EfCore/OpenApi
  references and package readmes. Corrected the ComplexProperty claim: EF
  Core cannot bind the interval's private constructor as a complex type, and
  column materialization would bypass the Start <= End invariant — the JSON
  column is the supported shape.

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.

1 participant