Skip to content

Add BoundedInt<TBounds> with custom-bounds witness pattern (closes #59) - #79

Open
KaliCZ wants to merge 4 commits into
mainfrom
claude/custom-type-definition-Ez4YZ
Open

Add BoundedInt<TBounds> with custom-bounds witness pattern (closes #59)#79
KaliCZ wants to merge 4 commits into
mainfrom
claude/custom-type-definition-Ez4YZ

Conversation

@KaliCZ

Copy link
Copy Markdown
Owner

Summary

Closes#59. Adds BoundedInt<TBounds> — an int constrained to a closed [Min, Max] range carried by a witness type implementing IBounds<int>, so e.g. BoundedInt<PageSizeBounds> makes the 1..100 invariant part of the signature.

publicreadonlystructPageSizeBounds:IBounds<int>{publicstaticintMin=>1;publicstaticintMax=>100;}voidGetUsers(BoundedInt<PageSizeBounds>pageSize){}

While I was here I also opened up the JSON converter factory to recognize any struct carrying [NumericWrapper], not just the four built-ins. Combined with the existing source generator, defining a brand-new validated numeric wrapper is now just an attribute and a Value / TryCreate pair — the rest (Create, equality, comparison, operators, JSON round-trip, Min / Max / Unwrap extensions) is generated. Readme has a new "Defining your own validated wrapper" section showing the recipe.

What's in the diff

  • src/StrongTypes/Numbers/IBounds.cs — the witness interface with static abstract Min/Max.
  • src/StrongTypes/Numbers/BoundedInt.cs — the struct itself. Stores an offset relative to TBounds.Min so default(BoundedInt<TBounds>) wraps Min and satisfies the invariant. The dynamic "must be between … and …" error message is achieved by passing an interpolated InvariantDescription through to the source generator, which emits it inside the partial type body where TBounds is in scope.
  • NumericStrongTypeJsonConverterFactoryCanConvert now matches by [NumericWrapper] attribute presence; underlying type is read off Value so BoundedInt<TBounds> works without a special case.
  • StrongTypesConvention (EF Core) + MissingEfCorePackageAnalyzer — both now recognize BoundedInt<>.
  • NumberExtensionsAsBounded<TBounds> / ToBounded<TBounds> on int.
  • Tests — BoundedIntTests.cs (FsCheck property tests for in/out-of-range, boundary fixed-cases, default-invariant, equality/comparison, JSON round-trip, error-message content, extensions); analyzer test theory gets a new Detects_bounded_int_wrapper case.
  • Readmes — main + EfCore.

Per the comment on #59 about the readme number-line diagram, the actual SVG isn't included — let me know if you'd like me to follow up with one.

Test plan

  • dotnet build (could not run locally — no SDK in this sandbox; CI will tell us)
  • dotnet test src/StrongTypes.TestsBoundedIntTests covers the issue's three test requirements (in-range round-trip, out-of-range rejection by both TryCreate and Create, boundary acceptance) plus generated-member branch coverage and a JSON round-trip.
  • dotnet test src/StrongTypes.Analyzers.Tests — new Detects_bounded_int_wrapper fact.
  • Manual: confirm IntelliSense for BoundedInt<PageSizeBounds>.Create(0) shows the dynamic "between 1 and 100 (inclusive)" message at runtime.

https://claude.ai/code/session_01TsE6jPbPttZztM6h2g7KqR


Generated by Claude Code

@github-actions

github-actionsBot commented Apr 26, 2026

Copy link
Copy Markdown

Coverage

Lines: 3923 / 5326 (73.7%) Branches: 2072 / 2800 (74.0%)

Files changed in this PR

FileLinesBranches
StrongTypes/Numbers/BoundedInt.cs11 / 11 (100.0%)4 / 4 (100.0%)
StrongTypes/Numbers/NumberExtensions.cs14 / 14 (100.0%)4 / 4 (100.0%)
StrongTypes/Numbers/NumericStrongTypeJsonConverterFactory.cs39 / 39 (100.0%)5 / 6 (83.3%)
StrongTypes.Analyzers/MissingEfCorePackageAnalyzer.cs141 / 154 (91.6%)53 / 62 (85.5%)
StrongTypes.Api/Controllers/BoundedIntEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
StrongTypes.Api/Data/PostgreSqlDbContext.cs28 / 28 (100.0%)0 / 0 (n/a)
StrongTypes.Api/Data/SqlServerDbContext.cs28 / 28 (100.0%)0 / 0 (n/a)
StrongTypes.Api/Entities/Entities.cs2 / 2 (100.0%)0 / 0 (n/a)
StrongTypes.EfCore/StrongTypesConvention.cs47 / 61 (77.0%)30 / 46 (65.2%)
StrongTypes.EfCore/UnwrapMethodCallTranslator.cs31 / 31 (100.0%)10 / 12 (83.3%)
StrongTypes.OpenApi.Core/NumericWrapperPainter.cs20 / 20 (100.0%)4 / 6 (66.7%)
StrongTypes.OpenApi.Core/StrongTypeSchemaTypes.cs55 / 60 (91.7%)39 / 48 (81.2%)
StrongTypes.OpenApi.Microsoft/Startup.cs15 / 15 (100.0%)0 / 0 (n/a)
StrongTypes.OpenApi.Microsoft/Numbers/BoundedIntSchemaTransformer.cs8 / 8 (100.0%)2 / 2 (100.0%)
StrongTypes.OpenApi.Swashbuckle/Startup.cs13 / 13 (100.0%)0 / 0 (n/a)
StrongTypes.OpenApi.Swashbuckle/Numbers/BoundedIntSchemaFilter.cs8 / 8 (100.0%)4 / 4 (100.0%)
StrongTypes.OpenApi.TestApi.Shared/BasicControllers.cs0 / 60 (0.0%)0 / 0 (n/a)
StrongTypes.OpenApi.TestApi.Shared/Models.cs2 / 48 (4.2%)0 / 0 (n/a)
StrongTypes — lines 91.5% (1568/1714), branches 86.7% (1014/1170)
Booleans — lines 100.0% (14/14), branches 84.6% (22/26)
FileLinesBranches
BooleanExtensions.cs2 / 2 (100.0%)2 / 2 (100.0%)
BooleanMapExtensions.cs12 / 12 (100.0%)20 / 24 (83.3%)
Collections — lines 90.2% (276/306), branches 85.1% (114/134)
FileLinesBranches
IEnumerableExtensions.cs7 / 7 (100.0%)6 / 6 (100.0%)
IEnumerableExtensions_Concatenating.cs2 / 2 (100.0%)2 / 2 (100.0%)
IEnumerableExtensions_Flattening.cs1 / 1 (100.0%)2 / 2 (100.0%)
IEnumerableExtensions_Null.cs5 / 5 (100.0%)10 / 10 (100.0%)
IEnumerableExtensions_Partition.cs11 / 11 (100.0%)6 / 6 (100.0%)
IEnumerableExtensions_Types.cs0 / 8 (0.0%)0 / 6 (0.0%)
NonEmptyEnumerable.cs59 / 65 (90.8%)30 / 38 (78.9%)
NonEmptyEnumerableDebugView.cs0 / 2 (0.0%)0 / 0 (n/a)
NonEmptyEnumerableExtensions.cs144 / 149 (96.6%)37 / 38 (97.4%)
NonEmptyEnumerableJsonConverter.cs47 / 50 (94.0%)21 / 26 (80.8%)
ReadOnlyList.cs0 / 6 (0.0%)0 / 0 (n/a)
Digits — lines 97.7% (85/87), branches 93.8% (30/32)
FileLinesBranches
Digit.cs78 / 80 (97.5%)26 / 28 (92.9%)
DigitExtensions.cs7 / 7 (100.0%)4 / 4 (100.0%)
Emails — lines 98.6% (68/69), branches 92.5% (37/40)
FileLinesBranches
Email.cs50 / 50 (100.0%)27 / 30 (90.0%)
EmailExtensions.cs10 / 11 (90.9%)8 / 8 (100.0%)
EmailJsonConverter.cs8 / 8 (100.0%)2 / 2 (100.0%)
Enums — lines 100.0% (58/58), branches 95.5% (21/22)
FileLinesBranches
EnumExtensions.cs58 / 58 (100.0%)21 / 22 (95.5%)
Exceptions — lines 78.9% (15/19), branches 50.0% (7/14)
FileLinesBranches
ExceptionEnumerableExtensions.cs15 / 19 (78.9%)7 / 14 (50.0%)
Maybe — lines 92.1% (151/164), branches 83.9% (94/112)
FileLinesBranches
Maybe.cs58 / 59 (98.3%)40 / 44 (90.9%)
MaybeCollectionExtensions.cs41 / 45 (91.1%)28 / 30 (93.3%)
MaybeExtensions.cs9 / 14 (64.3%)10 / 18 (55.6%)
MaybeJsonConverter.cs43 / 46 (93.5%)16 / 20 (80.0%)
Nullables — lines 100.0% (12/12), branches 83.3% (20/24)
FileLinesBranches
NullableMapExtensions.cs12 / 12 (100.0%)20 / 24 (83.3%)
Numbers — lines 100.0% (94/94), branches 95.5% (21/22)
FileLinesBranches
BoundedInt.cs11 / 11 (100.0%)4 / 4 (100.0%)
Negative.cs7 / 7 (100.0%)2 / 2 (100.0%)
NonNegative.cs7 / 7 (100.0%)2 / 2 (100.0%)
NonPositive.cs7 / 7 (100.0%)2 / 2 (100.0%)
NumberExtensions.cs14 / 14 (100.0%)4 / 4 (100.0%)
NumericStrongTypeJsonConverterFactory.cs39 / 39 (100.0%)5 / 6 (83.3%)
NumericWrapperAttribute.cs2 / 2 (100.0%)0 / 0 (n/a)
Positive.cs7 / 7 (100.0%)2 / 2 (100.0%)
Result — lines 98.5% (333/338), branches 95.6% (417/436)
FileLinesBranches
Result.cs68 / 70 (97.1%)55 / 68 (80.9%)
ResultAccessExtensions.cs4 / 4 (100.0%)8 / 8 (100.0%)
ResultAggregate.cs188 / 188 (100.0%)318 / 318 (100.0%)
ResultCatch.cs20 / 20 (100.0%)0 / 0 (n/a)
ResultFlattenExtensions.cs7 / 7 (100.0%)8 / 8 (100.0%)
ResultFromNullableExtensions.cs10 / 10 (100.0%)15 / 20 (75.0%)
ResultPartitionExtensions.cs20 / 20 (100.0%)5 / 6 (83.3%)
ResultThrowIfErrorExtensions.cs16 / 19 (84.2%)8 / 8 (100.0%)
Strings — lines 84.7% (133/157), branches 84.4% (54/64)
FileLinesBranches
NonEmptyString.cs87 / 87 (100.0%)32 / 32 (100.0%)
NonEmptyStringExtensions.cs13 / 27 (48.1%)0 / 0 (n/a)
NonEmptyStringJsonConverter.cs12 / 15 (80.0%)4 / 6 (66.7%)
StringExtensions.cs21 / 28 (75.0%)18 / 26 (69.2%)
generated — lines 83.1% (329/396), branches 72.5% (177/244)
FileLinesBranches
BoundedInt<T>.Extensions.g.cs0 / 23 (0.0%)0 / 16 (0.0%)
BoundedInt<T>.g.cs38 / 49 (77.6%)13 / 20 (65.0%)
Negative<T>.Extensions.g.cs32 / 32 (100.0%)20 / 20 (100.0%)
Negative<T>.g.cs45 / 49 (91.8%)24 / 32 (75.0%)
NonNegative<T>.Extensions.g.cs32 / 32 (100.0%)20 / 20 (100.0%)
NonNegative<T>.g.cs36 / 49 (73.5%)18 / 32 (56.2%)
NonPositive<T>.Extensions.g.cs32 / 32 (100.0%)20 / 20 (100.0%)
NonPositive<T>.g.cs36 / 49 (73.5%)18 / 32 (56.2%)
Positive<T>.Extensions.g.cs32 / 32 (100.0%)20 / 20 (100.0%)
Positive<T>.g.cs46 / 49 (93.9%)24 / 32 (75.0%)
StrongTypes.Analyzers — lines 94.7% (323/341), branches 86.8% (132/152)
(root) — lines 94.7% (323/341), branches 86.8% (132/152)
FileLinesBranches
AddEfCorePackageCodeFixProvider.cs48 / 52 (92.3%)18 / 20 (90.0%)
AddOpenApiPackageCodeFixProvider.cs52 / 53 (98.1%)21 / 26 (80.8%)
MissingEfCorePackageAnalyzer.cs141 / 154 (91.6%)53 / 62 (85.5%)
MissingOpenApiPackageAnalyzer.cs82 / 82 (100.0%)40 / 44 (90.9%)
StrongTypes.Api — lines 96.8% (332/343), branches 86.3% (88/102)
(root) — lines 100.0% (11/11), branches n/a (0/0)
FileLinesBranches
Program.cs11 / 11 (100.0%)0 / 0 (n/a)
Controllers — lines 96.9% (222/229), branches 86.3% (88/102)
FileLinesBranches
BindingProbeController.cs55 / 62 (88.7%)32 / 34 (94.1%)
BoundedIntEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
CollectionJsonController.cs4 / 4 (100.0%)0 / 0 (n/a)
EmailEntityController.cs44 / 44 (100.0%)26 / 30 (86.7%)
EntityControllerBase.cs9 / 9 (100.0%)0 / 0 (n/a)
NegativeDecimalEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NegativeDoubleEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NegativeFloatEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NegativeIntEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NegativeLongEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NegativeShortEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonEmptyStringEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonNegativeDecimalEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonNegativeDoubleEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonNegativeFloatEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonNegativeIntEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonNegativeLongEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonNegativeShortEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonPositiveDecimalEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonPositiveDoubleEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonPositiveFloatEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonPositiveIntEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonPositiveLongEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
NonPositiveShortEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
PositiveDecimalEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
PositiveDoubleEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
PositiveFloatEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
PositiveIntEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
PositiveLongEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
PositiveShortEntityController.cs1 / 1 (100.0%)0 / 0 (n/a)
ReferenceTypeEntityControllerBase.cs42 / 42 (100.0%)14 / 18 (77.8%)
StructTypeEntityControllerBase.cs42 / 42 (100.0%)16 / 20 (80.0%)
Data — lines 100.0% (56/56), branches n/a (0/0)
FileLinesBranches
PostgreSqlDbContext.cs28 / 28 (100.0%)0 / 0 (n/a)
SqlServerDbContext.cs28 / 28 (100.0%)0 / 0 (n/a)
Entities — lines 77.8% (14/18), branches n/a (0/0)
FileLinesBranches
Entities.cs2 / 2 (100.0%)0 / 0 (n/a)
EntityBase.cs12 / 12 (100.0%)0 / 0 (n/a)
IEntity.cs0 / 4 (0.0%)0 / 0 (n/a)
Models — lines 100.0% (29/29), branches n/a (0/0)
FileLinesBranches
CollectionJsonModels.cs20 / 20 (100.0%)0 / 0 (n/a)
EntityModels.cs9 / 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)
FileLinesBranches
JsonValidationErrorKeyNormalizer.cs22 / 22 (100.0%)20 / 20 (100.0%)
ModelMetadataNullability.cs12 / 13 (92.3%)7 / 10 (70.0%)
NonEmptyEnumerableModelBinder.cs51 / 62 (82.3%)25 / 30 (83.3%)
NonEmptyEnumerableModelBinderProvider.cs9 / 9 (100.0%)4 / 4 (100.0%)
StringElementParser.cs16 / 19 (84.2%)2 / 4 (50.0%)
StrongTypesAspNetCoreOptions.cs2 / 2 (100.0%)0 / 0 (n/a)
StrongTypesServiceCollectionExtensions.cs39 / 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)
FileLinesBranches
Program.cs6 / 6 (100.0%)0 / 0 (n/a)
Controllers — lines 89.2% (33/37), branches 92.5% (37/40)
FileLinesBranches
BindingProbeController.cs29 / 29 (100.0%)37 / 40 (92.5%)
JsonBodyProbeController.cs4 / 8 (50.0%)0 / 0 (n/a)
StrongTypes.EfCore — lines 88.8% (119/134), branches 70.0% (42/60)
(root) — lines 88.8% (119/134), branches 70.0% (42/60)
FileLinesBranches
MailAddressValueConverter.cs3 / 3 (100.0%)0 / 0 (n/a)
NonEmptyStringValueConverter.cs3 / 3 (100.0%)0 / 0 (n/a)
NumericStrongTypeValueConverter.cs17 / 17 (100.0%)0 / 0 (n/a)
StrongTypesConvention.cs47 / 61 (77.0%)30 / 46 (65.2%)
StrongTypesDbContextOptionsExtension.cs18 / 19 (94.7%)2 / 2 (100.0%)
UnwrapMethodCallTranslator.cs31 / 31 (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)
FileLinesBranches
Generators.cs96 / 124 (77.4%)0 / 0 (n/a)
StrongTypes.OpenApi.Core — lines 90.6% (445/491), branches 82.0% (292/356)
(root) — lines 90.8% (275/303), branches 84.2% (170/202)
FileLinesBranches
NumericWrapperKinds.cs11 / 11 (100.0%)0 / 0 (n/a)
NumericWrapperPainter.cs20 / 20 (100.0%)4 / 6 (66.7%)
PrimitiveSchemaMap.cs19 / 19 (100.0%)0 / 0 (n/a)
SchemaPaint.cs82 / 89 (92.1%)67 / 82 (81.7%)
StrongTypeInlineMarker.cs6 / 6 (100.0%)6 / 6 (100.0%)
StrongTypeSchemaTypes.cs55 / 60 (91.7%)39 / 48 (81.2%)
WrapperAnnotationApplier.cs82 / 98 (83.7%)54 / 60 (90.0%)
Inlining — lines 90.4% (170/188), branches 79.2% (122/154)
FileLinesBranches
StrongTypeInliner.cs170 / 188 (90.4%)122 / 154 (79.2%)
StrongTypes.OpenApi.Microsoft — lines 39.3% (435/1108), branches 40.8% (174/426)
(root) — lines 95.0% (247/260), branches 75.4% (98/130)
FileLinesBranches
MicrosoftSchemaNaming.cs52 / 52 (100.0%)8 / 8 (100.0%)
PropertyAnnotationSchemaTransformer.cs77 / 89 (86.5%)52 / 74 (70.3%)
Startup.cs15 / 15 (100.0%)0 / 0 (n/a)
StrongTypesComponentSchemaFiller.cs103 / 104 (99.0%)38 / 48 (79.2%)
Binding — lines 98.1% (103/105), branches 78.6% (55/70)
FileLinesBranches
NonBodyStrongTypeOperationTransformer.cs103 / 105 (98.1%)55 / 70 (78.6%)
Collections — lines 100.0% (19/19), branches 100.0% (8/8)
FileLinesBranches
NonEmptyEnumerableSchemaTransformer.cs10 / 10 (100.0%)2 / 2 (100.0%)
StrongTypeCollectionShapeTransformer.cs9 / 9 (100.0%)6 / 6 (100.0%)
Digits — lines 100.0% (11/11), branches 100.0% (2/2)
FileLinesBranches
DigitSchemaTransformer.cs11 / 11 (100.0%)2 / 2 (100.0%)
Emails — lines 100.0% (11/11), branches 100.0% (2/2)
FileLinesBranches
EmailSchemaTransformer.cs11 / 11 (100.0%)2 / 2 (100.0%)
Inlining — lines 100.0% (7/7), branches 50.0% (1/2)
FileLinesBranches
StrongTypeInliningDocumentTransformer.cs7 / 7 (100.0%)1 / 2 (50.0%)
Maybe — lines 100.0% (12/12), branches 100.0% (2/2)
FileLinesBranches
MaybeSchemaTransformer.cs12 / 12 (100.0%)2 / 2 (100.0%)
Numbers — lines 100.0% (16/16), branches 100.0% (4/4)
FileLinesBranches
BoundedIntSchemaTransformer.cs8 / 8 (100.0%)2 / 2 (100.0%)
NumericStrongTypeSchemaTransformer.cs8 / 8 (100.0%)2 / 2 (100.0%)
Strings — lines 100.0% (9/9), branches 100.0% (2/2)
FileLinesBranches
NonEmptyStringSchemaTransformer.cs9 / 9 (100.0%)2 / 2 (100.0%)
obj/Debug/net10.0/Microsoft.AspNetCore.OpenApi.SourceGenerators/Microsoft.AspNetCore.OpenApi.SourceGenerators.XmlCommentGenerator — lines 0.0% (0/658), branches 0.0% (0/204)
FileLinesBranches
OpenApiXmlCommentSupport.generated.cs0 / 658 (0.0%)0 / 204 (0.0%)
StrongTypes.OpenApi.Swashbuckle — lines 94.1% (239/254), branches 76.7% (155/202)
(root) — lines 98.6% (68/69), branches 90.3% (56/62)
FileLinesBranches
PropertyAnnotationSchemaFilter.cs55 / 56 (98.2%)56 / 62 (90.3%)
Startup.cs13 / 13 (100.0%)0 / 0 (n/a)
Binding — lines 88.3% (106/120), branches 64.3% (72/112)
FileLinesBranches
NonBodyStrongTypeOperationFilter.cs106 / 120 (88.3%)72 / 112 (64.3%)
Collections — lines 100.0% (6/6), branches 100.0% (4/4)
FileLinesBranches
NonEmptyEnumerableSchemaFilter.cs6 / 6 (100.0%)4 / 4 (100.0%)
Digits — lines 100.0% (10/10), branches 100.0% (4/4)
FileLinesBranches
DigitSchemaFilter.cs10 / 10 (100.0%)4 / 4 (100.0%)
Emails — lines 100.0% (10/10), branches 100.0% (4/4)
FileLinesBranches
EmailSchemaFilter.cs10 / 10 (100.0%)4 / 4 (100.0%)
Inlining — lines 100.0% (2/2), branches n/a (0/0)
FileLinesBranches
StrongTypeInliningDocumentFilter.cs2 / 2 (100.0%)0 / 0 (n/a)
Maybe — lines 100.0% (13/13), branches 75.0% (3/4)
FileLinesBranches
MaybeSchemaFilter.cs13 / 13 (100.0%)3 / 4 (75.0%)
Numbers — lines 100.0% (16/16), branches 100.0% (8/8)
FileLinesBranches
BoundedIntSchemaFilter.cs8 / 8 (100.0%)4 / 4 (100.0%)
NumericStrongTypeSchemaFilter.cs8 / 8 (100.0%)4 / 4 (100.0%)
Strings — lines 100.0% (8/8), branches 100.0% (4/4)
FileLinesBranches
NonEmptyStringSchemaFilter.cs8 / 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)
FileLinesBranches
Program.cs14 / 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)
FileLinesBranches
OpenApiXmlCommentSupport.generated.cs149 / 384 (38.8%)63 / 204 (30.9%)
StrongTypes.OpenApi.TestApi.Shared — lines 1.0% (2/196), branches n/a (0/0)
(root) — lines 1.0% (2/196), branches n/a (0/0)
FileLinesBranches
AnnotatedRequests.cs0 / 84 (0.0%)0 / 0 (n/a)
BasicControllers.cs0 / 60 (0.0%)0 / 0 (n/a)
CustomAnnotationsControllers.cs0 / 4 (0.0%)0 / 0 (n/a)
Models.cs2 / 48 (4.2%)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)
FileLinesBranches
Program.cs11 / 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 89.28571% with 3 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
src/StrongTypes.EfCore/StrongTypesConvention.cs60.00%2 Missing ⚠️
...s/Numbers/NumericStrongTypeJsonConverterFactory.cs85.71%0 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
@@ Coverage Diff @@## main #79 +/- ##
==========================================
+ Coverage 89.07% 89.10% +0.02% 
==========================================
Files 85 86 +1 Lines 1877 1890 +13 Branches 408 409 +1 ==========================================
+ Hits 1672 1684 +12 - Misses 134 135 +1 
Partials 71 71 
ComponentsCoverage Δ
StrongTypes89.77% <95.00%> (+0.08%)⬆️
StrongTypes.Analyzers86.89% <100.00%> (+0.06%)⬆️
StrongTypes.EfCore81.74% <60.00%> (-0.52%)⬇️
StrongTypes.Api97.34% <ø> (ø)
StrongTypes.FsCheck77.27% <ø> (ø)
Files with missing linesCoverage Δ
...ongTypes.Analyzers/MissingEfCorePackageAnalyzer.cs86.36% <100.00%> (+0.08%)⬆️
src/StrongTypes/Numbers/BoundedInt.cs100.00% <100.00%> (ø)
src/StrongTypes/Numbers/NumberExtensions.cs100.00% <100.00%> (ø)
...s/Numbers/NumericStrongTypeJsonConverterFactory.cs97.05% <85.71%> (-0.24%)⬇️
src/StrongTypes.EfCore/StrongTypesConvention.cs65.51% <60.00%> (-0.56%)⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

claudeand others added 3 commits June 24, 2026 09:39
Introduces an int constrained to [TBounds.Min, TBounds.Max] via a small
witness type implementing IBounds<int>. Bounds travel with the type, so
e.g. `BoundedInt<PageSizeBounds>` makes the 1..100 invariant part of
the signature.
Also opens up the JSON converter factory to any struct marked with
[NumericWrapper], so users can spin up their own validated numeric
wrappers with just the attribute and a `Value` / `TryCreate` pair —
the source generator and JSON converter take care of the rest.
EF Core convention and the missing-EfCore-package analyzer now also
recognize BoundedInt<>.
When a [NumericWrapper] type uses a concrete value type (e.g. int) as its
underlying type, IEquatable<T>.Equals(T) and IComparable<T>.CompareTo(T)
require the unannotated parameter type. Emitting Equals(int? other) /
CompareTo(int? other) is Nullable<int>, which doesn't satisfy the interface
contracts. For generic-parameter underlyings (e.g. T : INumber<T>), T?
is a nullable annotation only, so the existing form remains correct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the parts of the BoundedInt feature that didn't exist when the
original PR was opened (the OpenApi.*, Api.IntegrationTests, and AspNetCore
projects all landed on main since), and fills the EF Core gap.
- OpenAPI: BoundedInt renders as `{ type: integer, format: int32, minimum,
maximum }` on both the Microsoft and Swashbuckle pipelines. The witness
bounds are read off the closed type's static Min/Max at schema time (the
range can't be derived from the generic definition alone). Schema tests
cover value, nullable-value, and a dedicated nullables endpoint across all
three document variants.
- Api integration: BoundedIntEntity round-trips through the request pipeline
and EF Core (both providers) via the shared EntityTests suite, which also
pins the 400 ValidationProblemDetails shape and the `$.value` /
`$.nullableValue` error keys.
- EF Core: BoundedIntExtensions added to the Unwrap method-call translator so
`.Unwrap()` translates to a bare int column, matching the convention that
already recognizes BoundedInt<>.
- Unit tests: full-range witness (int.MinValue..int.MaxValue) pins the
offset-storage modular round-trip; the error-message test now asserts the
witness bounds are interpolated, not left as a literal placeholder.
- Docs: Skill numeric/openapi references and the OpenApi package readmes now
cover BoundedInt and the custom-wrapper recipe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KaliCZ
KaliCZforce-pushed the claude/custom-type-definition-Ez4YZ branch from b0bd53c to 98a8b86CompareJune 24, 2026 08:07
WPF: BoundedInt<TBounds> is a scalar IParsable value, so it belongs in the
WPF TypeConverter wiring alongside the sign wrappers. Adds it to the type
description provider, a PageSize property + PageSizeBounds witness on the
test view-model, and a BoundedIntBindingTests class (OneWay display, TwoWay
valid, out-of-range and non-numeric validation errors). WPF readme + Skill
WPF reference updated.
Docs: the original BoundedInt PR missed WPF, the EF Core Unwrap translator,
and OpenAPI because the guidance only covered *test* projects, never the
production wiring. Adds an "Adding a new strong type — integration checklist"
to CLAUDE.md enumerating every package a type must be wired through (core,
EF Core convention + Unwrap translator, both OpenAPI pipelines, WPF,
analyzers), with pointers to testing.md and CONTRIBUTING for tests and docs.
testing.md gains the missing WPF row and a WPF binding-test section;
CONTRIBUTING points at the checklist.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

Add BoundedRange numeric type (e.g. for pagination 1..100)

3 participants

@KaliCZ@codecov-commenter@claude