Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
When creating a complex type mapping using a schema transformer the values that are transformed do not copy to a nullable version of that type.
This is the extension method I am using to test this
public static void MapType(this OpenApiOptions options, Type typeToMap, Func<OpenApiSchema> schemaFunc)
{
options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
if (context.JsonTypeInfo.Type == typeToMap)
{
var targetSchema = schemaFunc();
schema.Type = targetSchema.Type;
schema.Pattern = targetSchema.Pattern;
}
return Task.CompletedTask;
});
}
"NullableOfUserId": {
"type": "object",
"nullable": true
},
"UserId": {
"pattern": "^guid-pattern$",
"type": "string",
},
Notice in this example that the value was transformed from type: object
to type: string
as well as the pattern: "^guid-pattern$"
(yes I'm aware that's not a valid regex for a guid) are not copied to the NullabeOfUserId
.
NOTE: Normally there would also be a properties
property on these objects however, I omitted them for brevity and in an example like this you would likely convert the the string using a JsonConverter.
Expected Behavior
When adding a schema transformer I would expect the properties to copy to the nullable equivalent of the value.
"NullableOfUserId": {
"type": "object",
"nullable": true
},
"UserId": {
"pattern": "^guid-pattern$",
"type": "string",
},
should be
"NullableOfUserId": {
"pattern": "^guid-pattern$",
"type": "string",
"nullable": true
},
"UserId": {
"pattern": "^guid-pattern$",
"type": "string",
},
Steps To Reproduce
Program.cs
using AspNetOpenApiRepro;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi(options =>
{
options.MapType(typeof(UserId), () =>
{
return new OpenApiSchema()
{
Type = "string",
Pattern = "^guid-pattern$"
};
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", Ok<WeatherForecast?> () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)],
null,
new UserId(Guid.NewGuid())
))
.ToArray()
.FirstOrDefault();
return TypedResults.Ok<WeatherForecast?>(forecast);
})
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary, UserId? Id, UserId OtherId)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
public readonly record struct UserId(Guid Value);
Some extension class for making testing easier
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;
namespace AspNetOpenApiRepro;
public static class OpenApiExtensions
{
public static void MapType(this OpenApiOptions options, Type typeToMap, Func<OpenApiSchema> schemaFunc)
{
options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
if (context.JsonTypeInfo.Type == typeToMap)
{
var targetSchema = schemaFunc();
schema.Type = targetSchema.Type;
schema.Pattern = targetSchema.Pattern;
}
return Task.CompletedTask;
});
}
}
Exceptions (if any)
N/A
.NET Version
9.0.102
Anything else?
Closest Issue
The most similar issue that I found was #59976 but that seemed like it was likely a different problem.
Full Open API Spec
{
"openapi": "3.0.1",
"info": {
"title": "AspNetOpenApiRepro | v1",
"version": "1.0.0"
},
"servers": [
{
"url": "https://localhost:7164"
},
{
"url": "http://localhost:5090"
}
],
"paths": {
"/weatherforecast": {
"get": {
"tags": [
"AspNetOpenApiRepro"
],
"operationId": "GetWeatherForecast",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WeatherForecast"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"NullableOfUserId": {
"type": "object",
"properties": {
"value": {
"type": "string",
"format": "uuid"
}
},
"nullable": true
},
"UserId": {
"pattern": "^guid-pattern$",
"type": "string",
"properties": {
"value": {
"type": "string",
"format": "uuid"
}
}
},
"WeatherForecast": {
"required": [
"date",
"temperatureC",
"summary",
"id",
"otherId"
],
"type": "object",
"properties": {
"date": {
"type": "string",
"format": "date"
},
"temperatureC": {
"type": "integer",
"format": "int32"
},
"summary": {
"type": "string",
"nullable": true
},
"id": {
"$ref": "#/components/schemas/NullableOfUserId"
},
"otherId": {
"$ref": "#/components/schemas/UserId"
},
"temperatureF": {
"type": "integer",
"format": "int32"
}
}
}
}
},
"tags": [
{
"name": "AspNetOpenApiRepro"
}
]
}