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
Open
Show file tree
Hide file tree
Changes from 3 commits
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
2 changes: 1 addition & 1 deletion eng/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" />
<LatestPackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" />
<LatestPackageReference Include="Microsoft.OpenApi" />
<LatestPackageReference Include="Microsoft.OpenApi.Readers" />
<LatestPackageReference Include="Microsoft.OpenApi.YamlReader" />
<LatestPackageReference Include="System.Buffers" />
<LatestPackageReference Include="System.CodeDom" />
<LatestPackageReference Include="System.CommandLine.Experimental" />
Expand Down
4 changes: 2 additions & 2 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,8 @@
<XunitExtensibilityExecutionVersion>$(XunitVersion)</XunitExtensibilityExecutionVersion>
<XUnitRunnerVisualStudioVersion>2.8.2</XUnitRunnerVisualStudioVersion>
<MicrosoftDataSqlClientVersion>5.2.2</MicrosoftDataSqlClientVersion>
<MicrosoftOpenApiVersion>2.0.0-preview.11</MicrosoftOpenApiVersion>
<MicrosoftOpenApiReadersVersion>2.0.0-preview.11</MicrosoftOpenApiReadersVersion>
<MicrosoftOpenApiVersion>2.0.0-preview.17</MicrosoftOpenApiVersion>
<MicrosoftOpenApiYamlReaderVersion>2.0.0-preview.17</MicrosoftOpenApiYamlReaderVersion>
<!-- dotnet tool versions (see also auto-updated DotnetEfVersion property). -->
<DotnetDumpVersion>6.0.322601</DotnetDumpVersion>
<DotnetServeVersion>1.10.93</DotnetServeVersion>
Expand Down
1 change: 0 additions & 1 deletion src/OpenApi/OpenApi.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests.csproj",
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.Tests\\Microsoft.AspNetCore.OpenApi.Tests.csproj",
"src\\OpenApi\\sample\\Sample.csproj",
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj"
]
}
Expand Down
35 changes: 31 additions & 4 deletions src/OpenApi/gen/XmlCommentGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Any;

Expand Down Expand Up @@ -341,9 +342,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name);
if (operationParameter is not null)
{
var targetOperationParameter = operationParameter is OpenApiParameterReference reference
? reference.Target
: (OpenApiParameter)operationParameter;
var targetOperationParameter = UnwrapOpenApiParameter(operationParameter);
targetOperationParameter.Description = parameterComment.Description;
if (parameterComment.Example is { } jsonString)
{
Expand All @@ -359,7 +358,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
requestBody.Description = parameterComment.Description;
if (parameterComment.Example is { } jsonString)
{
foreach (var mediaType in requestBody.Content.Values)
var content = requestBody?.Content?.Values;
if (content is null)
{
continue;
}
foreach (var mediaType in content)
{
mediaType.Example = jsonString.Parse();
}
Expand All @@ -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?

{
if (sourceParameter is OpenApiParameterReference parameterReference)
{
if (parameterReference.Target is OpenApiParameter target)
{
return target;
}
else
{
throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}.");
}
}
else if (sourceParameter is OpenApiParameter directParameter)
{
return directParameter;
}
else
{
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

}
}
}

{{GeneratedCodeAttribute}}
Expand Down
26 changes: 13 additions & 13 deletions src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Text;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.OpenApi.Models;

internal static class ApiDescriptionExtensions
{
/// <summary>
/// Maps the HTTP method of the ApiDescription to the OpenAPI <see cref="OperationType"/> .
/// Maps the HTTP method of the ApiDescription to the HttpMethod.
/// </summary>
/// <param name="apiDescription">The ApiDescription to resolve an operation type from.</param>
/// <returns>The <see cref="OperationType"/> associated with the given <paramref name="apiDescription"/>.</returns>
public static OperationType GetOperationType(this ApiDescription apiDescription) =>
/// <param name="apiDescription">The ApiDescription to resolve an HttpMethod from.</param>
/// <returns>The <see cref="HttpMethod"/> associated with the given <paramref name="apiDescription"/>.</returns>
public static HttpMethod GetHttpMethod(this ApiDescription apiDescription) =>
apiDescription.HttpMethod?.ToUpperInvariant() switch
{
"GET" => OperationType.Get,
"POST" => OperationType.Post,
"PUT" => OperationType.Put,
"DELETE" => OperationType.Delete,
"PATCH" => OperationType.Patch,
"HEAD" => OperationType.Head,
"OPTIONS" => OperationType.Options,
"TRACE" => OperationType.Trace,
"GET" => HttpMethod.Get,
"POST" => HttpMethod.Post,
"PUT" => HttpMethod.Put,
"DELETE" => HttpMethod.Delete,
"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.

};

Expand Down
2 changes: 1 addition & 1 deletion src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ internal static void MapPolymorphismOptionsToDiscriminator(this JsonNode schema,
// that we hardcode here. We could use `OpenApiReference` to construct the reference and
// serialize it but we use a hardcoded string here to avoid allocating a new object and
// working around Microsoft.OpenApi's serialization libraries.
mappings[$"{discriminator}"] = $"#/components/schemas/{createSchemaReferenceId(context.TypeInfo)}{createSchemaReferenceId(jsonDerivedType)}";
mappings[$"{discriminator}"] = $"{createSchemaReferenceId(context.TypeInfo)}{createSchemaReferenceId(jsonDerivedType)}";
}
}
schema[OpenApiSchemaKeywords.DiscriminatorKeyword] = polymorphismOptions.TypeDiscriminatorPropertyName;
Expand Down
23 changes: 15 additions & 8 deletions src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;
using OpenApiConstants = Microsoft.AspNetCore.OpenApi.OpenApiConstants;

internal sealed partial class OpenApiJsonSchema
Expand Down Expand Up @@ -256,12 +258,12 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
case OpenApiSchemaKeywords.MinimumKeyword:
reader.Read();
var minimum = reader.GetDecimal();
schema.Minimum = minimum;
schema.Minimum = minimum.ToString(CultureInfo.InvariantCulture);
break;
case OpenApiSchemaKeywords.MaximumKeyword:
reader.Read();
var maximum = reader.GetDecimal();
schema.Maximum = maximum;
schema.Maximum = maximum.ToString(CultureInfo.InvariantCulture);
break;
case OpenApiSchemaKeywords.PatternKeyword:
reader.Read();
Expand Down Expand Up @@ -302,25 +304,30 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
var mappings = ReadDictionary<string>(ref reader);
if (mappings is not null)
{
schema.Discriminator.Mapping = mappings;
schema.Discriminator ??= new OpenApiDiscriminator();
foreach (var kvp in mappings)
{
schema.Discriminator.Mapping ??= [];
schema.Discriminator.Mapping[kvp.Key] = new OpenApiSchemaReference(kvp.Value);
}
}
break;
case OpenApiConstants.SchemaId:
reader.Read();
schema.Annotations ??= new Dictionary<string, object>();
schema.Annotations.Add(OpenApiConstants.SchemaId, reader.GetString());
schema.Metadata ??= [];
schema.Metadata.Add(OpenApiConstants.SchemaId, reader.GetString() ?? string.Empty);
break;
// OpenAPI does not support the `const` keyword in its schema implementation, so
// we map it to its closest approximation, an enum with a single value, here.
case OpenApiSchemaKeywords.ConstKeyword:
reader.Read();
schema.Enum = [ReadJsonNode(ref reader, out var constType)];
schema.Enum = ReadJsonNode(ref reader, out var constType) is { } jsonNode ? [jsonNode] : [];
schema.Type = constType;
break;
case OpenApiSchemaKeywords.RefKeyword:
reader.Read();
schema.Annotations ??= new Dictionary<string, object>();
schema.Annotations[OpenApiConstants.RefId] = reader.GetString();
schema.Metadata ??= [];
schema.Metadata[OpenApiConstants.RefId] = reader.GetString() ?? string.Empty;
break;
default:
reader.Skip();
Expand Down
24 changes: 12 additions & 12 deletions src/OpenApi/src/Services/OpenApiConstants.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.OpenApi.Models;
using System.Net.Http;

namespace Microsoft.AspNetCore.OpenApi;

Expand All @@ -14,19 +14,19 @@ internal static class OpenApiConstants
internal const string SchemaId = "x-schema-id";
internal const string RefId = "x-ref-id";
internal const string DefaultOpenApiResponseKey = "default";
// Since there's a finite set of operation types that can be included in a given
// OpenApiPaths, we can pre-allocate an array of these types and use a direct
// Since there's a finite set of HTTP methods that can be included in a given
// OpenApiPaths, we can pre-allocate an array of these methods and use a direct
// lookup on the OpenApiPaths dictionary to avoid allocating an enumerator
// over the KeyValuePairs in OpenApiPaths.
internal static readonly OperationType[] OperationTypes = [
OperationType.Get,
OperationType.Post,
OperationType.Put,
OperationType.Delete,
OperationType.Options,
OperationType.Head,
OperationType.Patch,
OperationType.Trace
internal static readonly HttpMethod[] HttpMethods = [
HttpMethod.Get,
HttpMethod.Post,
HttpMethod.Put,
HttpMethod.Delete,
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.

];
// Represents primitive types that should never be represented as
// a schema reference and always inlined.
Expand Down
Loading
Loading