Skip to content

Add Interval strong types with JSON and EF Core support (closes #60)#77

Open
KaliCZ wants to merge 3 commits into
mainfrom
claude/json-converter-intervals-pB8eY
Open

Add Interval strong types with JSON and EF Core support (closes #60)#77
KaliCZ wants to merge 3 commits into
mainfrom
claude/json-converter-intervals-pB8eY

Conversation

@KaliCZ

@KaliCZ KaliCZ commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

Implements #60. Introduces four interval struct types covering every nullability combination so the C# compiler enforces "closed interval ⇒ non-nullable endpoints" — there is no runtime check needed because the type system already won't let a caller pass null for the wrong endpoint.

Type Start End
ClosedInterval<T> T T
IntervalFrom<T> T T?
IntervalUntil<T> T? T
Interval<T> T? T?

All four are readonly struct with where T : struct, IComparable<T>, follow the TryCreate / Create factory pattern, enforce Start <= End whenever both endpoints are present, and ship a Deconstruct method so the 4-arm switch from #60 works:

var label = interval switch
{
    (null, null)         => "unbounded",
    (null, { } end)      => $"up to {end}",
    ({ } start, null)    => $"from {start}",
    ({ } start, { } end) => $"{start}..{end}",
};

JSON

IntervalJsonConverterFactory handles all four shapes through System.Text.Json. Wire format is {"Start": …, "End": …} and honours the active JsonNamingPolicy. JSON values that violate the wrapper invariant (e.g. start > end, or null for a non-nullable endpoint) throw JsonException.

EF Core

Two persistence shapes are offered:

  1. One JSON columnIntervalJsonValueConverter<TInterval> plus entity.HasIntervalJsonConversion(e => e.Interval).
  2. Two scalar columns — use EF Core's standard entity.ComplexProperty(e => e.Interval). The interval's Start and End become two columns whose nullability follows the interval variant automatically (closed → both NOT NULL, IntervalFrom → start NOT NULL/end nullable, etc.).

Tests

  • FsCheck arbitraries for all four types (always satisfying Start <= End).
  • Property tests cover validation, deconstruction, equality, and JSON round-trip across every nullability shape.
  • Targeted facts for the rejection cases (Start > End, null for a non-nullable endpoint, missing properties).

Test plan

  • CI: dotnet build succeeds with TreatWarningsAsErrors=true.
  • CI: StrongTypes.Tests passes (the Intervals/ folder and the new Generators arbitraries).
  • Manual: confirm the issue's 4-arm pattern-match example compiles against Interval<DateTime>.

⚠️ I could not run dotnet build locally — the dev environment doesn't have the .NET SDK installed, so CI is the first place this code is exercised.

https://claude.ai/code/session_01Qc2Xf1PTwgi9jPSBGbv8f6


Generated by Claude Code

@KaliCZ KaliCZ self-assigned this Apr 26, 2026
@github-actions

github-actions Bot commented Apr 26, 2026

Copy link
Copy Markdown

Coverage

Lines: 4115 / 5502 (74.8%)    Branches: 2153 / 2860 (75.3%)

Files changed in this PR

File Lines Branches
StrongTypes/Intervals/ClosedInterval.cs 17 / 18 (94.4%) 8 / 10 (80.0%)
StrongTypes/Intervals/Interval.cs 29 / 30 (96.7%) 23 / 24 (95.8%)
StrongTypes/Intervals/IntervalFrom.cs 22 / 24 (91.7%) 15 / 16 (93.8%)
StrongTypes/Intervals/IntervalJsonConverterFactory.cs 95 / 99 (96.0%) 30 / 34 (88.2%)
StrongTypes/Intervals/IntervalUntil.cs 22 / 24 (91.7%) 15 / 16 (93.8%)
StrongTypes.Api/Controllers/ClosedIntervalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
StrongTypes.Api/Controllers/IntervalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
StrongTypes.Api/Controllers/IntervalFromEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
StrongTypes.Api/Controllers/IntervalUntilEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
StrongTypes.Api/Data/IntervalEntityConfiguration.cs 11 / 11 (100.0%) 0 / 0 (n/a)
StrongTypes.Api/Data/PostgreSqlDbContext.cs 32 / 32 (100.0%) 0 / 0 (n/a)
StrongTypes.Api/Data/SqlServerDbContext.cs 32 / 32 (100.0%) 0 / 0 (n/a)
StrongTypes.EfCore/IntervalEfCoreExtensions.cs 1 / 1 (100.0%) 0 / 0 (n/a)
StrongTypes.EfCore/IntervalJsonValueConverter.cs 6 / 6 (100.0%) 0 / 0 (n/a)
StrongTypes.FsCheck/Generators.cs 116 / 144 (80.6%) 2 / 2 (100.0%)
StrongTypes.OpenApi.Core/StrongTypeSchemaTypes.cs 57 / 57 (100.0%) 45 / 50 (90.0%)
StrongTypes.OpenApi.Core/Inlining/StrongTypeInliner.cs 173 / 191 (90.6%) 124 / 156 (79.5%)
StrongTypes.OpenApi.Microsoft/Startup.cs 15 / 15 (100.0%) 0 / 0 (n/a)
StrongTypes.OpenApi.Microsoft/Intervals/IntervalSchemaTransformer.cs 15 / 15 (100.0%) 2 / 2 (100.0%)
StrongTypes.OpenApi.Swashbuckle/Startup.cs 13 / 13 (100.0%) 0 / 0 (n/a)
StrongTypes.OpenApi.Swashbuckle/Intervals/IntervalSchemaFilter.cs 17 / 17 (100.0%) 4 / 4 (100.0%)
StrongTypes.OpenApi.TestApi.Shared/BasicControllers.cs 0 / 63 (0.0%) 0 / 0 (n/a)
StrongTypes — lines 93.3% (1705/1827), branches 88.5% (1088/1230)
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%)
Intervals — lines 94.9% (185/195), branches 91.0% (91/100)
File Lines Branches
ClosedInterval.cs 17 / 18 (94.4%) 8 / 10 (80.0%)
Interval.cs 29 / 30 (96.7%) 23 / 24 (95.8%)
IntervalFrom.cs 22 / 24 (91.7%) 15 / 16 (93.8%)
IntervalJsonConverterFactory.cs 95 / 99 (96.0%) 30 / 34 (88.2%)
IntervalUntil.cs 22 / 24 (91.7%) 15 / 16 (93.8%)
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 97.0% (352/363), 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 97.0% (225/232), branches 86.3% (88/102)
File Lines Branches
BindingProbeController.cs 55 / 62 (88.7%) 32 / 34 (94.1%)
ClosedIntervalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
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)
IntervalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
IntervalFromEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
IntervalUntilEntityController.cs 1 / 1 (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% (75/75), branches n/a (0/0)
File Lines Branches
IntervalEntityConfiguration.cs 11 / 11 (100.0%) 0 / 0 (n/a)
PostgreSqlDbContext.cs 32 / 32 (100.0%) 0 / 0 (n/a)
SqlServerDbContext.cs 32 / 32 (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 90.6% (125/138), branches 73.2% (41/56)
(root) — lines 90.6% (125/138), branches 73.2% (41/56)
File Lines Branches
IntervalEfCoreExtensions.cs 1 / 1 (100.0%) 0 / 0 (n/a)
IntervalJsonValueConverter.cs 6 / 6 (100.0%) 0 / 0 (n/a)
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 47 / 59 (79.7%) 29 / 42 (69.0%)
StrongTypesDbContextOptionsExtension.cs 18 / 19 (94.7%) 2 / 2 (100.0%)
UnwrapMethodCallTranslator.cs 30 / 30 (100.0%) 10 / 12 (83.3%)
StrongTypes.FsCheck — lines 80.6% (116/144), branches 100.0% (2/2)
(root) — lines 80.6% (116/144), branches 100.0% (2/2)
File Lines Branches
Generators.cs 116 / 144 (80.6%) 2 / 2 (100.0%)
StrongTypes.OpenApi.Core — lines 91.5% (442/483), branches 83.5% (299/358)
(root) — lines 92.1% (269/292), branches 86.6% (175/202)
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 57 / 57 (100.0%) 45 / 50 (90.0%)
WrapperAnnotationApplier.cs 82 / 98 (83.7%) 54 / 60 (90.0%)
Inlining — lines 90.6% (173/191), branches 79.5% (124/156)
File Lines Branches
StrongTypeInliner.cs 173 / 191 (90.6%) 124 / 156 (79.5%)
StrongTypes.OpenApi.Microsoft — lines 39.1% (441/1127), branches 40.6% (173/426)
(root) — lines 94.6% (246/260), 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 15 / 15 (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%)
Intervals — lines 100.0% (15/15), branches 100.0% (2/2)
File Lines Branches
IntervalSchemaTransformer.cs 15 / 15 (100.0%) 2 / 2 (100.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/670), branches 0.0% (0/204)
File Lines Branches
OpenApiXmlCommentSupport.generated.cs 0 / 670 (0.0%) 0 / 204 (0.0%)
StrongTypes.OpenApi.Swashbuckle — lines 94.3% (248/263), branches 76.7% (155/202)
(root) — lines 98.6% (68/69), branches 90.3% (56/62)
File Lines Branches
PropertyAnnotationSchemaFilter.cs 55 / 56 (98.2%) 56 / 62 (90.3%)
Startup.cs 13 / 13 (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)
Intervals — lines 100.0% (17/17), branches 100.0% (4/4)
File Lines Branches
IntervalSchemaFilter.cs 17 / 17 (100.0%) 4 / 4 (100.0%)
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/196), branches n/a (0/0)
(root) — lines 0.0% (0/196), branches n/a (0/0)
File Lines Branches
AnnotatedRequests.cs 0 / 84 (0.0%) 0 / 0 (n/a)
BasicControllers.cs 0 / 63 (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%)

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 74.29907% with 55 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ongTypes/Intervals/IntervalJsonConverterFactory.cs 80.21% 11 Missing and 7 partials ⚠️
src/StrongTypes/Intervals/Interval.cs 60.00% 11 Missing and 1 partial ⚠️
src/StrongTypes/Intervals/IntervalFrom.cs 70.83% 6 Missing and 1 partial ⚠️
src/StrongTypes/Intervals/IntervalUntil.cs 70.83% 6 Missing and 1 partial ⚠️
...c/StrongTypes.EfCore/IntervalJsonValueConverter.cs 0.00% 6 Missing ⚠️
src/StrongTypes/Intervals/ClosedInterval.cs 77.77% 2 Missing and 2 partials ⚠️
src/StrongTypes.EfCore/IntervalEfCoreExtensions.cs 0.00% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
@@            Coverage Diff             @@
##             main      #77      +/-   ##
==========================================
- Coverage   89.07%   87.56%   -1.52%     
==========================================
  Files          85       92       +7     
  Lines        1877     2091     +214     
  Branches      408      447      +39     
==========================================
+ Hits         1672     1831     +159     
- Misses        134      177      +43     
- Partials       71       83      +12     
Components Coverage Δ
StrongTypes 87.63% <74.33%> (-2.06%) ⬇️
StrongTypes.Analyzers 86.82% <ø> (ø)
StrongTypes.EfCore 77.86% <0.00%> (-4.40%) ⬇️
StrongTypes.Api 97.34% <ø> (ø)
StrongTypes.FsCheck 80.76% <100.00%> (+3.49%) ⬆️
Files with missing lines Coverage Δ
src/StrongTypes.FsCheck/Generators.cs 80.76% <100.00%> (+3.49%) ⬆️
src/StrongTypes.EfCore/IntervalEfCoreExtensions.cs 0.00% <0.00%> (ø)
src/StrongTypes/Intervals/ClosedInterval.cs 77.77% <77.77%> (ø)
...c/StrongTypes.EfCore/IntervalJsonValueConverter.cs 0.00% <0.00%> (ø)
src/StrongTypes/Intervals/IntervalFrom.cs 70.83% <70.83%> (ø)
src/StrongTypes/Intervals/IntervalUntil.cs 70.83% <70.83%> (ø)
src/StrongTypes/Intervals/Interval.cs 60.00% <60.00%> (ø)
...ongTypes/Intervals/IntervalJsonConverterFactory.cs 80.21% <80.21%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

claude and others added 2 commits June 24, 2026 09:38
Introduce four interval struct types covering every nullability combination
so the C# compiler enforces "closed interval ⇒ non-nullable endpoints":

- ClosedInterval<T>: (T Start, T End)             both bounded
- IntervalFrom<T>:   (T Start, T? End)            "from X"
- IntervalUntil<T>:  (T? Start, T End)            "until Y"
- Interval<T>:       (T? Start, T? End)           fully open

All four enforce Start <= End wherever both endpoints are present,
follow the TryCreate / Create factory pattern, and ship a Deconstruct
method that enables the 4-arm switch from issue #60.

Persistence:
- IntervalJsonConverterFactory handles all four shapes through System.Text.Json.
- IntervalJsonValueConverter<TInterval> stores the interval as a JSON string
  column in EF Core; alternatively, callers can map to two scalar columns
  via EF Core's standard ComplexProperty (no custom converter needed).

Tests:
- FsCheck arbitraries for each type (always satisfying start <= end).
- Property tests cover validation, deconstruction, equality, and JSON
  round-trip across all four nullability shapes.

https://claude.ai/code/session_01Qc2Xf1PTwgi9jPSBGbv8f6
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>
@KaliCZ KaliCZ force-pushed the claude/json-converter-intervals-pB8eY branch from c63f864 to e6f0bf5 Compare June 24, 2026 09:45
@KaliCZ

KaliCZ commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto latest main and added a hardening commit. Highlights:

  • Error-path fix (matches Normalize JSON request-body validation error keys #106): the converter's nested endpoint reads lost the property path — a null/type-mismatch endpoint surfaced the validation error at the document root $ instead of $.value. Now rethrows path-less so System.Text.Json reattaches the path, exactly as Normalize JSON request-body validation error keys #106 did for the numeric converter. Error messages also no longer leak the ClosedInterval`1 arity name.
  • API integration tests: four interval entities/controllers mapped via HasIntervalJsonConversion; wire→DB round-trip on SQL Server + PostgreSQL, null handling, update, and invalid payloads asserted at $.value.
  • OpenAPI: the original PR shipped no schema support (intervals rendered opaque). Added a Microsoft schema transformer and a Swashbuckle filter rendering { Start, End } with required: [Start, End] and per-variant endpoint nullability; verified on Microsoft 3.0, Microsoft 3.1, and Swashbuckle.
  • Expanded unit tests: Contains/ToString per variant, JSON edge cases (missing/extra/reordered keys, non-object tokens, DateOnly/DateTime endpoints), and generator-branch coverage facts.
  • Docs: README, SKILL.md + references/intervals.md, and the EfCore/OpenApi references and package readmes.

⚠️ Correction to the description above: EF Core ComplexProperty is not a supported persistence shape — EF can't bind the interval's private constructor as a complex type, and column-by-column materialization would bypass the Start <= End invariant. The single JSON column (HasIntervalJsonConversion, which round-trips through the validating converter) is the supported shape; docs were corrected accordingly.

Full build passes with TreatWarningsAsErrors=true; unit + API + OpenAPI suites green (API/OpenAPI run locally with STRONGTYPES_SKIP_SQLSERVER=1 on an ARM64 box — CI exercises the real SQL Server path).

WPF two-way binding goes through ParsableTypeConverter<T> (T : IParsable<T>),
which the type-description provider only synthesises for the scalar strong
types (NonEmptyString, Email, Digit, numeric wrappers). The intervals — like
Maybe<T> and NonEmptyEnumerable<T> — are composite, have no single-TextBox
string form, and are correctly not registered. The readme and skill claimed
"every strong type"; tighten both to say string-round-trippable types and
point composite types at field-by-field binding.

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.

3 participants