Skip to content

Update to Microsoft.OpenApi v2.0.0-preview.17 #61541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

captainsafia
Copy link
Member

@captainsafia captainsafia commented Apr 17, 2025

  • React to nullable annotations changes in types
  • React to OperationType -> HttpMethod move
  • React to Annotations -> Metadata rename
  • React to discriminator mapping using OpenApiSchemaReference
  • React to Reader package being renamed to YamlReader

@captainsafia captainsafia requested review from a team and wtgodbe as code owners April 17, 2025 21:46
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Apr 17, 2025
@captainsafia captainsafia added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Apr 17, 2025
// Sort schemas by key name for better readability and consistency
// This works around an API change in OpenAPI.NET
document.Components.Schemas = new Dictionary<string, IOpenApiSchema>(
document.Components.Schemas.OrderBy(kvp => kvp.Key),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this does anything once you insert the items into the dictionary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done as the final step before returning the document to the consumer. The expectation is that new schemas aren't inserted into the dictionary after that. We could use FrozenDictionary/ImmutableDictionary to help codify these but the new APIs use Dictionary instead of IDictionary so we're constrained here. :/

@@ -661,7 +677,7 @@ private async Task<OpenApiRequestBody> GetFormRequestBody(
var contentType = requestFormat.MediaType;
requestBody.Content[contentType] = new OpenApiMediaType
{
Schema = schema
Schema = complexTypeSchema ?? schema
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not following this change. Can you explain it? And does it need a new test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working a bit around some of the new strictness in the APIs. The IOpenApiSchema has two implementations OpenApiSchema which represents a resolved schema and has set/get on all properties and OpenApiSchemaaReference which is a pointer to a schema and only exposes a get on properties.

The schema variable represents a resolved schema that we construct and populate with form parameters depending on some specific rules. For example, if you have multiple parameters annotated with [FromForm] like in this test we generate a schema that uses allOf to indicate that all of the properties in each type should appear in a single form payload.

When there is a single parameter annotated with [FromForm] and it is a complex type then the binding implementation resolves from its properties directly. This test validates that behavior.

@@ -383,6 +387,29 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform

return Task.CompletedTask;
}

private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this - I might borrow it for Swashbuckle 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm -- I wonder if it might be worth integrating into the Microsoft.OpenAPI APIs? Let me know how it shakes out for you in practice and we can consider moving it to the base library?

"PATCH" => HttpMethod.Patch,
"HEAD" => HttpMethod.Head,
"OPTIONS" => HttpMethod.Options,
"TRACE" => HttpMethod.Trace,
_ => throw new InvalidOperationException($"Unsupported HTTP method: {apiDescription.HttpMethod}"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC there's a bug logged somewhere about an exception being throw for the empty string in MVC instead of being a GET. If you're touching this area maybe fix that here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the right fix for that bug is to fix the ApiExplorer layer so that GET is the assumed default for MVC controller actions that don't explicitly set it. That way we can retain the behavior of throwing for actually invalid HTTP methods here.

HttpMethod.Options,
HttpMethod.Head,
HttpMethod.Patch,
HttpMethod.Trace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen an issue that there's a new QUERY method coming soon, so that might need adding shortly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, the OpenAPI spec doesn't yet support all HTTP methods. There's been discussion of loosening this requirement in the past but at the moment it only supports this fixed set.

@@ -31,17 +31,25 @@ public void MapRelativePathToItemPath_ReturnsItemPathForApiDescription(string re
Assert.Equal(expectedItemPath, itemPath);
}

public static class HttpMethodTestData
{
public static IEnumerable<object[]> TestCases => new List<object[]>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding my comment about QUERY, is it worth adding a reflection-based test that checks for HttpMethod having any new static property to flush out the need to react to any addition?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can probably forgo on this until the spec supports more HTTP methods. From my understanding, moving from OperationType to HttpMethod in this breaking change was a setup for that.

@@ -407,7 +407,7 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
}
else
{
throw new InvalidOperationException("The input schema must be an OpenApiParameter or OpenApiParameterReference.");
throw new InvalidOperationException("The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new InvalidOperationException("The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}.");
throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}.");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg I hate myself

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one did make me feel bad 😅 when I spotted it in the snapshots

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's ok I groaned harder on the other whitespace issue

I really wish there was a nicer templating story for source generated files so you could catch these kinds of things more easily

@@ -338,6 +338,8 @@ await VerifyOpenApiDocument(action, document =>
[([MinLength(2)] int[] id) => {}, (OpenApiSchema schema) => Assert.Equal(2, schema.MinItems)],
[([Length(4, 8)] int[] id) => {}, (OpenApiSchema schema) => { Assert.Equal(4, schema.MinItems); Assert.Equal(8, schema.MaxItems); }],
[([Range(4, 8)]int id) => {}, (OpenApiSchema schema) => { Assert.Equal("4", schema.Minimum); Assert.Equal("8", schema.Maximum); }],
[([Range(1234, 5678)]int id) => {}, (OpenApiSchema schema) => { Assert.Equal("1234", schema.Minimum); Assert.Equal("5678", schema.Maximum); }],
[([Range(1234.56, 7891.1)] decimal id) => {}, (OpenApiSchema schema) => { Assert.Equal("1234.56", schema.Minimum); Assert.Equal("7891.1", schema.Maximum); }],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[([Range(1234.56, 7891.1)] decimal id) => {}, (OpenApiSchema schema) => { Assert.Equal("1234.56", schema.Minimum); Assert.Equal("7891.1", schema.Maximum); }],
[([Range(1234.56, 7891.1)] decimal id) => {}, (OpenApiSchema schema) => { Assert.Equal("1234.56", schema.Minimum); Assert.Equal("7891.1", schema.Maximum); }],

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:groan:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants