Skip to content

Commit 58ac2f7

Browse files
authored
Upgrade to OpenAPI.NET v2.0.0-preview7 (#60269)
1 parent e64a99d commit 58ac2f7

File tree

29 files changed

+337
-310
lines changed

29 files changed

+337
-310
lines changed

eng/Versions.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,8 @@
335335
<XunitExtensibilityExecutionVersion>$(XunitVersion)</XunitExtensibilityExecutionVersion>
336336
<XUnitRunnerVisualStudioVersion>2.8.2</XUnitRunnerVisualStudioVersion>
337337
<MicrosoftDataSqlClientVersion>5.2.2</MicrosoftDataSqlClientVersion>
338-
<MicrosoftOpenApiVersion>2.0.0-preview5</MicrosoftOpenApiVersion>
339-
<MicrosoftOpenApiReadersVersion>2.0.0-preview5</MicrosoftOpenApiReadersVersion>
338+
<MicrosoftOpenApiVersion>2.0.0-preview7</MicrosoftOpenApiVersion>
339+
<MicrosoftOpenApiReadersVersion>2.0.0-preview7</MicrosoftOpenApiReadersVersion>
340340
<!-- dotnet tool versions (see also auto-updated DotnetEfVersion property). -->
341341
<DotnetDumpVersion>6.0.322601</DotnetDumpVersion>
342342
<DotnetServeVersion>1.10.93</DotnetServeVersion>

src/OpenApi/gen/XmlCommentGenerator.Emitter.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated
5555
using Microsoft.AspNetCore.Mvc.Controllers;
5656
using Microsoft.Extensions.DependencyInjection;
5757
using Microsoft.OpenApi.Models;
58+
using Microsoft.OpenApi.Models.References;
5859
using Microsoft.OpenApi.Any;
5960
6061
{{GeneratedCodeAttribute}}
@@ -256,12 +257,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
256257
var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name);
257258
if (operationParameter is not null)
258259
{
259-
operationParameter.Description = parameterComment.Description;
260+
var targetOperationParameter = operationParameter is OpenApiParameterReference reference
261+
? reference.Target
262+
: (OpenApiParameter)operationParameter;
263+
targetOperationParameter.Description = parameterComment.Description;
260264
if (parameterComment.Example is { } jsonString)
261265
{
262-
operationParameter.Example = jsonString.Parse();
266+
targetOperationParameter.Example = jsonString.Parse();
263267
}
264-
operationParameter.Deprecated = parameterComment.Deprecated;
268+
targetOperationParameter.Deprecated = parameterComment.Deprecated;
265269
}
266270
else
267271
{

src/OpenApi/sample/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Json.Serialization;
45
using Microsoft.OpenApi.Models;
56
using Sample.Transformers;
67

@@ -11,6 +12,13 @@
1112
#pragma warning restore IL2026
1213
builder.Services.AddAuthentication().AddJwtBearer();
1314

15+
// Supports representing integer formats as strictly numerically values
16+
// inside the schema.
17+
builder.Services.ConfigureHttpJsonOptions(options =>
18+
{
19+
options.SerializerOptions.NumberHandling = JsonNumberHandling.Strict;
20+
});
21+
1422
builder.Services.AddOpenApi("v1", options =>
1523
{
1624
options.AddHeader("X-Version", "1.0");

src/OpenApi/sample/Transformers/AddBearerSecuritySchemeTransformer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.AspNetCore.Authentication;
55
using Microsoft.AspNetCore.OpenApi;
66
using Microsoft.OpenApi.Models;
7+
using Microsoft.OpenApi.Models.Interfaces;
78

89
namespace Sample.Transformers;
910

@@ -14,7 +15,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf
1415
var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
1516
if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
1617
{
17-
var requirements = new Dictionary<string, OpenApiSecurityScheme>
18+
var requirements = new Dictionary<string, IOpenApiSecurityScheme>
1819
{
1920
["Bearer"] = new OpenApiSecurityScheme
2021
{

src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,15 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable
120120
}
121121
else if (attribute is MinLengthAttribute minLengthAttribute)
122122
{
123-
var targetKey = schema[OpenApiSchemaKeywords.TypeKeyword]?.GetValue<string>() == "array" ? OpenApiSchemaKeywords.MinItemsKeyword : OpenApiSchemaKeywords.MinLengthKeyword;
124-
schema[targetKey] = minLengthAttribute.Length;
123+
if (MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes &&
124+
schemaTypes.HasFlag(JsonSchemaType.Array))
125+
{
126+
schema[OpenApiSchemaKeywords.MinItemsKeyword] = minLengthAttribute.Length;
127+
}
128+
else
129+
{
130+
schema[OpenApiSchemaKeywords.MinLengthKeyword] = minLengthAttribute.Length;
131+
}
125132
}
126133
else if (attribute is LengthAttribute lengthAttribute)
127134
{
@@ -191,14 +198,13 @@ internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSch
191198
var underlyingType = Nullable.GetUnderlyingType(type);
192199
if (_simpleTypeToOpenApiSchema.TryGetValue(underlyingType ?? type, out var openApiSchema))
193200
{
194-
schema[OpenApiSchemaKeywords.NullableKeyword] = openApiSchema.Nullable || (schema[OpenApiSchemaKeywords.TypeKeyword] is JsonArray schemaType && schemaType.GetValues<string>().Contains("null"));
195-
schema[OpenApiSchemaKeywords.TypeKeyword] = openApiSchema.Type.ToString();
201+
if (underlyingType != null && MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes &&
202+
!schemaTypes.HasFlag(JsonSchemaType.Null))
203+
{
204+
schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString();
205+
}
196206
schema[OpenApiSchemaKeywords.FormatKeyword] = openApiSchema.Format;
197207
schema[OpenApiConstants.SchemaId] = createSchemaReferenceId(context.TypeInfo);
198-
schema[OpenApiSchemaKeywords.NullableKeyword] = underlyingType != null;
199-
// Clear out patterns that the underlying JSON schema generator uses to represent
200-
// validations for DateTime, DateTimeOffset, and integers.
201-
schema[OpenApiSchemaKeywords.PatternKeyword] = null;
202208
}
203209
}
204210

@@ -334,14 +340,17 @@ internal static void ApplyParameterInfo(this JsonNode schema, ApiParameterDescri
334340
schema.ApplyRouteConstraints(constraints);
335341
}
336342

337-
if (parameterDescription.Source is { } bindingSource && SupportsNullableProperty(bindingSource))
343+
if (parameterDescription.Source is { } bindingSource
344+
&& SupportsNullableProperty(bindingSource)
345+
&& MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes &&
346+
schemaTypes.HasFlag(JsonSchemaType.Null))
338347
{
339-
schema[OpenApiSchemaKeywords.NullableKeyword] = false;
348+
schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes & ~JsonSchemaType.Null).ToString();
340349
}
341350

342351
// Parameters sourced from the header, query, route, and/or form cannot be nullable based on our binding
343352
// rules but can be optional.
344-
static bool SupportsNullableProperty(BindingSource bindingSource) =>bindingSource == BindingSource.Header
353+
static bool SupportsNullableProperty(BindingSource bindingSource) => bindingSource == BindingSource.Header
345354
|| bindingSource == BindingSource.Query
346355
|| bindingSource == BindingSource.Path
347356
|| bindingSource == BindingSource.Form
@@ -435,9 +444,11 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, Parameter
435444

436445
var nullabilityInfoContext = new NullabilityInfoContext();
437446
var nullabilityInfo = nullabilityInfoContext.Create(parameterInfo);
438-
if (nullabilityInfo.WriteState == NullabilityState.Nullable)
447+
if (nullabilityInfo.WriteState == NullabilityState.Nullable
448+
&& MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes
449+
&& !schemaTypes.HasFlag(JsonSchemaType.Null))
439450
{
440-
schema[OpenApiSchemaKeywords.NullableKeyword] = true;
451+
schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString();
441452
}
442453
}
443454

@@ -452,7 +463,54 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, JsonPrope
452463
// all schema (no type, no format, no constraints).
453464
if (propertyInfo.PropertyType != typeof(object) && (propertyInfo.IsGetNullable || propertyInfo.IsSetNullable))
454465
{
455-
schema[OpenApiSchemaKeywords.NullableKeyword] = true;
466+
if (MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes &&
467+
!schemaTypes.HasFlag(JsonSchemaType.Null))
468+
{
469+
schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString();
470+
}
471+
}
472+
}
473+
474+
private static JsonSchemaType? MapJsonNodeToSchemaType(JsonNode? jsonNode)
475+
{
476+
if (jsonNode is not JsonArray jsonArray)
477+
{
478+
if (Enum.TryParse<JsonSchemaType>(jsonNode?.GetValue<string>(), true, out var openApiSchemaType))
479+
{
480+
return openApiSchemaType;
481+
}
482+
483+
return jsonNode is JsonValue jsonValue && jsonValue.TryGetValue<string>(out var identifier)
484+
? ToSchemaType(identifier)
485+
: null;
486+
}
487+
488+
JsonSchemaType? schemaType = null;
489+
490+
foreach (var node in jsonArray)
491+
{
492+
if (node is JsonValue jsonValue && jsonValue.TryGetValue<string>(out var identifier))
493+
{
494+
var type = ToSchemaType(identifier);
495+
schemaType = schemaType.HasValue ? (schemaType | type) : type;
496+
}
497+
}
498+
499+
return schemaType;
500+
501+
static JsonSchemaType ToSchemaType(string identifier)
502+
{
503+
return identifier.ToLowerInvariant() switch
504+
{
505+
"null" => JsonSchemaType.Null,
506+
"boolean" => JsonSchemaType.Boolean,
507+
"integer" => JsonSchemaType.Integer,
508+
"number" => JsonSchemaType.Number,
509+
"string" => JsonSchemaType.String,
510+
"array" => JsonSchemaType.Array,
511+
"object" => JsonSchemaType.Object,
512+
_ => throw new InvalidOperationException($"Unknown schema type: {identifier}"),
513+
};
456514
}
457515
}
458516
}

src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.OpenApi.Models;
5+
using Microsoft.OpenApi.Models.Interfaces;
56
using Microsoft.OpenApi.Models.References;
67

78
namespace Microsoft.AspNetCore.OpenApi;
89

910
internal static class OpenApiDocumentExtensions
1011
{
1112
/// <summary>
12-
/// Registers a <see cref="OpenApiSchema" /> into the top-level components store on the
13+
/// Registers a <see cref="IOpenApiSchema" /> into the top-level components store on the
1314
/// <see cref="OpenApiDocument" /> and returns a resolvable reference to it.
1415
/// </summary>
1516
/// <param name="document">The <see cref="OpenApiDocument"/> to register the schema onto.</param>
1617
/// <param name="schemaId">The ID that serves as the key for the schema in the schema store.</param>
17-
/// <param name="schema">The <see cref="OpenApiSchema" /> to register into the document.</param>
18-
/// <returns>An <see cref="OpenApiSchema"/> with a reference to the stored schema.</returns>
19-
public static OpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument document, string schemaId, OpenApiSchema schema)
18+
/// <param name="schema">The <see cref="IOpenApiSchema" /> to register into the document.</param>
19+
/// <returns>An <see cref="IOpenApiSchema"/> with a reference to the stored schema.</returns>
20+
public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument document, string schemaId, IOpenApiSchema schema)
2021
{
2122
document.Components ??= new();
22-
document.Components.Schemas ??= new Dictionary<string, OpenApiSchema>();
23+
document.Components.Schemas ??= new Dictionary<string, IOpenApiSchema>();
2324
document.Components.Schemas[schemaId] = schema;
2425
document.Workspace ??= new();
2526
var location = document.BaseUri + "/components/schemas/" + schemaId;

src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text.Json.Serialization;
99
using Microsoft.AspNetCore.OpenApi;
1010
using Microsoft.OpenApi.Models;
11+
using Microsoft.OpenApi.Models.Interfaces;
1112
using OpenApiConstants = Microsoft.AspNetCore.OpenApi.OpenApiConstants;
1213

1314
internal sealed partial class OpenApiJsonSchema
@@ -220,10 +221,6 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
220221
var valueConverter = (JsonConverter<OpenApiJsonSchema>)options.GetTypeInfo(typeof(OpenApiJsonSchema)).Converter;
221222
schema.Items = valueConverter.Read(ref reader, typeof(OpenApiJsonSchema), options)?.Schema;
222223
break;
223-
case OpenApiSchemaKeywords.NullableKeyword:
224-
reader.Read();
225-
schema.Nullable = reader.GetBoolean();
226-
break;
227224
case OpenApiSchemaKeywords.DescriptionKeyword:
228225
reader.Read();
229226
schema.Description = reader.GetString();
@@ -274,7 +271,7 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
274271
case OpenApiSchemaKeywords.PropertiesKeyword:
275272
reader.Read();
276273
var props = ReadDictionary<OpenApiJsonSchema>(ref reader);
277-
schema.Properties = props?.ToDictionary(p => p.Key, p => p.Value.Schema);
274+
schema.Properties = props?.ToDictionary(p => p.Key, p => p.Value.Schema as IOpenApiSchema);
278275
break;
279276
case OpenApiSchemaKeywords.AdditionalPropertiesKeyword:
280277
reader.Read();
@@ -290,7 +287,7 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
290287
reader.Read();
291288
schema.Type = JsonSchemaType.Object;
292289
var schemas = ReadList<OpenApiJsonSchema>(ref reader);
293-
schema.AnyOf = schemas?.Select(s => s.Schema).ToList();
290+
schema.AnyOf = schemas?.Select(s => s.Schema as IOpenApiSchema).ToList();
294291
break;
295292
case OpenApiSchemaKeywords.DiscriminatorKeyword:
296293
reader.Read();
@@ -322,7 +319,8 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName,
322319
break;
323320
case OpenApiSchemaKeywords.RefKeyword:
324321
reader.Read();
325-
schema.Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = reader.GetString() };
322+
schema.Annotations ??= new Dictionary<string, object>();
323+
schema.Annotations[OpenApiConstants.RefId] = reader.GetString();
326324
break;
327325
default:
328326
reader.Skip();

src/OpenApi/src/Schemas/OpenApiSchemaKeywords.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ internal class OpenApiSchemaKeywords
1212
public const string AnyOfKeyword = "anyOf";
1313
public const string EnumKeyword = "enum";
1414
public const string DefaultKeyword = "default";
15-
public const string NullableKeyword = "nullable";
1615
public const string DescriptionKeyword = "description";
1716
public const string DiscriminatorKeyword = "discriminatorName";
1817
public const string DiscriminatorMappingKeyword = "discriminatorMapping";

src/OpenApi/src/Services/OpenApiConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal static class OpenApiConstants
1212
internal const string DefaultOpenApiRoute = "/openapi/{documentName}.json";
1313
internal const string DescriptionId = "x-aspnetcore-id";
1414
internal const string SchemaId = "x-schema-id";
15+
internal const string RefId = "x-ref-id";
1516
internal const string DefaultOpenApiResponseKey = "default";
1617
// Since there's a finite set of operation types that can be included in a given
1718
// OpenApiPaths, we can pre-allocate an array of these types and use a direct

0 commit comments

Comments
 (0)