Swashbuckle (Swagger) schema plumbing for Kalicz.StrongTypes.
Out of the box Swashbuckle.AspNetCore describes the raw CLR shape of strong
types — so a NonEmptyString property shows up as a class with a Value field,
Positive<int> as a wrapper object, and Maybe<T> as the full union surface.
This package registers schema filters that rewrite those schemas to match the
JSON the converters actually emit, so generated clients and Swagger UI see the
real wire format.
If your app uses Microsoft.AspNetCore.OpenApi (AddOpenApi()) instead of
Swashbuckle, install Kalicz.StrongTypes.OpenApi.Microsoft
— it provides the same schema corrections against Microsoft's pipeline.
dotnet add package Kalicz.StrongTypes.OpenApi.Swashbucklebuilder.Services.AddSwaggerGen(options => options.AddStrongTypes());
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();NonEmptyString→{ "type": "string", "minLength": 1 }Positive<T>→ underlying primitive withminimum: 0, exclusiveMinimum: trueNonNegative<T>→ underlying primitive withminimum: 0Negative<T>→ underlying primitive withmaximum: 0, exclusiveMaximum: trueNonPositive<T>→ underlying primitive withmaximum: 0NonEmptyEnumerable<T>andINonEmptyEnumerable<T>→{ "type": "array", "minItems": 1, "items": <T schema> }Maybe<T>→ object wrapper{ "Value": <T schema> }matching the converter's on-the-wire format.
Both ASP.NET Core OpenAPI pipelines drop every ValidationAttribute on a
property whose CLR type carries a custom JsonConverter — which every
strong-type wrapper does — collapsing the property to a bare $ref to
the wrapper component.
This package re-applies them: every annotation Swashbuckle's
DataAnnotationsSchemaFilter natively writes for a primitive-typed property
(including any third-party schema filter you've registered) also reaches a
wrapper-typed one. Caller bounds compose with the wrapper's floor under
tighter-wins rules: the wrapper never relaxes a caller's constraint, and a
caller bound that would loosen the wrapper's floor is ignored.
public sealed record CreateUserRequest(
[StringLength(50, MinimumLength = 3)]
[RegularExpression("^[a-zA-Z0-9_]+$")]
NonEmptyString Username,
[Range(18, 120)]
Positive<int> Age,
[MaxLength(10)]
NonEmptyEnumerable<NonEmptyString> Tags,
[EmailAddress]
NonEmptyString ContactEmail,
[Url]
NonEmptyString Website);On the wire:
| Property | Resulting schema |
|---|---|
Username |
{ "type": "string", "minLength": 3, "maxLength": 50, "pattern": "^[a-zA-Z0-9_]+$" } |
Age |
{ "type": "integer", "format": "int32", "minimum": 18, "maximum": 120 } |
Tags |
{ "type": "array", "minItems": 1, "maxItems": 10, "items": { "type": "string", "minLength": 1 } } |
ContactEmail |
{ "type": "string", "minLength": 1, "format": "email" } |
Website |
{ "type": "string", "minLength": 1, "format": "uri" } |
Annotations Swashbuckle's filter doesn't natively map on primitive-typed
properties — e.g. [Description], [DefaultValue], [Length],
[Base64String] — aren't written here either, so the wrapper-typed
surface stays consistent with the primitive-typed one. If you also need
those, install
Kalicz.StrongTypes.OpenApi.Microsoft
and use Microsoft.AspNetCore.OpenApi.