Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/OpenApi/src/Services/OpenApiDocumentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,17 @@ private static Type GetTargetType(ApiDescription description, ApiParameterDescri
var parameterType = parameter.Type is not null
? Nullable.GetUnderlyingType(parameter.Type) ?? parameter.Type
: parameter.Type;
var requiresModelMetadataFallback = parameterType == typeof(string) && parameter.ModelMetadata.ModelType != parameter.Type;

// parameter.Type = typeof(string)
// parameter.ModelMetadata.Type = typeof(TEnum)
var requiresModelMetadataFallbackForEnum = parameterType == typeof(string)
&& parameter.ModelMetadata.ModelType != parameter.Type
&& parameter.ModelMetadata.ModelType.IsEnum;
// Enums are exempt because we want to set the OpenApiSchema.Enum field when feasible.
// parameter.Type = typeof(TEnum), typeof(Student), typeof(ITryParse)
// parameter.ModelMetadata.Type = typeof(string)
var hasTryParse = bindingMetadata?.HasTryParse == true && parameterType is not null && !parameterType.IsEnum;
var targetType = requiresModelMetadataFallback || hasTryParse
var targetType = requiresModelMetadataFallbackForEnum || hasTryParse
? parameter.ModelMetadata.ModelType
: parameter.Type;
targetType ??= typeof(string);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,26 +604,35 @@ await VerifyOpenApiDocument(action, document =>
[Route("/api/with-ambient-route-param/{versionId}")]
private void AmbientRouteParameter() { }

[Fact]
public async Task SupportsRouteParameterWithCustomTryParse()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task SupportsRouteParameterWithCustomTryParse(bool useAction)
{
// Arrange
var builder = CreateBuilder();

// Act
builder.MapGet("/api/{student}", (Student student) => { });
builder.MapGet("/api", () => new Student("Tester"));
if (!useAction)
{
builder.MapGet("/api/{student}", (Student student) => student);
await VerifyOpenApiDocument(builder, AssertOpenApiDocument);
}
else
{
var action = CreateActionDescriptor(nameof(GetStudent));
await VerifyOpenApiDocument(action, AssertOpenApiDocument);
}

// Assert
await VerifyOpenApiDocument(builder, document =>
static void AssertOpenApiDocument(OpenApiDocument document)
{
// Parameter is a plain-old string when it comes from the route or query
var operation = document.Paths["/api/{student}"].Operations[OperationType.Get];
var parameter = Assert.Single(operation.Parameters);
Assert.Equal("string", parameter.Schema.Type);

// Type is fully serialized in the response
operation = document.Paths["/api"].Operations[OperationType.Get];
var response = Assert.Single(operation.Responses).Value;
Assert.True(response.Content.TryGetValue("application/json", out var mediaType));
var schema = mediaType.Schema.GetEffective(document);
Expand All @@ -633,9 +642,12 @@ await VerifyOpenApiDocument(builder, document =>
Assert.Equal("name", property.Key);
Assert.Equal("string", property.Value.Type);
});
});
}
}

[Route("/api/{student}")]
private Student GetStudent(Student student) => student;

public record Student(string Name)
{
public static bool TryParse(string value, out Student result)
Expand Down
Loading