diff --git a/eng/Versions.props b/eng/Versions.props index 1021eb1bceba..75eb1ccf6fd1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -335,8 +335,8 @@ $(XunitVersion) 2.8.2 5.2.2 - 2.0.0-preview5 - 2.0.0-preview5 + 2.0.0-preview7 + 2.0.0-preview7 6.0.322601 1.10.93 diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index f91854fa7825..73896cf85ecb 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -55,6 +55,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Any; {{GeneratedCodeAttribute}} @@ -256,12 +257,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); if (operationParameter is not null) { - operationParameter.Description = parameterComment.Description; + var targetOperationParameter = operationParameter is OpenApiParameterReference reference + ? reference.Target + : (OpenApiParameter)operationParameter; + targetOperationParameter.Description = parameterComment.Description; if (parameterComment.Example is { } jsonString) { - operationParameter.Example = jsonString.Parse(); + targetOperationParameter.Example = jsonString.Parse(); } - operationParameter.Deprecated = parameterComment.Deprecated; + targetOperationParameter.Deprecated = parameterComment.Deprecated; } else { diff --git a/src/OpenApi/sample/Program.cs b/src/OpenApi/sample/Program.cs index 0db7b41a29c1..cc1899e40482 100644 --- a/src/OpenApi/sample/Program.cs +++ b/src/OpenApi/sample/Program.cs @@ -1,6 +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 System.Text.Json.Serialization; using Microsoft.OpenApi.Models; using Sample.Transformers; @@ -11,6 +12,13 @@ #pragma warning restore IL2026 builder.Services.AddAuthentication().AddJwtBearer(); +// Supports representing integer formats as strictly numerically values +// inside the schema. +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.NumberHandling = JsonNumberHandling.Strict; +}); + builder.Services.AddOpenApi("v1", options => { options.AddHeader("X-Version", "1.0"); diff --git a/src/OpenApi/sample/Transformers/AddBearerSecuritySchemeTransformer.cs b/src/OpenApi/sample/Transformers/AddBearerSecuritySchemeTransformer.cs index 02bd033b7628..05ea43c8580f 100644 --- a/src/OpenApi/sample/Transformers/AddBearerSecuritySchemeTransformer.cs +++ b/src/OpenApi/sample/Transformers/AddBearerSecuritySchemeTransformer.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.OpenApi; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; namespace Sample.Transformers; @@ -14,7 +15,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync(); if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer")) { - var requirements = new Dictionary + var requirements = new Dictionary { ["Bearer"] = new OpenApiSecurityScheme { diff --git a/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs b/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs index 0c1f8a8643b4..8ebc675598af 100644 --- a/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs +++ b/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs @@ -120,8 +120,15 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable } else if (attribute is MinLengthAttribute minLengthAttribute) { - var targetKey = schema[OpenApiSchemaKeywords.TypeKeyword]?.GetValue() == "array" ? OpenApiSchemaKeywords.MinItemsKeyword : OpenApiSchemaKeywords.MinLengthKeyword; - schema[targetKey] = minLengthAttribute.Length; + if (MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && + schemaTypes.HasFlag(JsonSchemaType.Array)) + { + schema[OpenApiSchemaKeywords.MinItemsKeyword] = minLengthAttribute.Length; + } + else + { + schema[OpenApiSchemaKeywords.MinLengthKeyword] = minLengthAttribute.Length; + } } else if (attribute is LengthAttribute lengthAttribute) { @@ -191,14 +198,13 @@ internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSch var underlyingType = Nullable.GetUnderlyingType(type); if (_simpleTypeToOpenApiSchema.TryGetValue(underlyingType ?? type, out var openApiSchema)) { - schema[OpenApiSchemaKeywords.NullableKeyword] = openApiSchema.Nullable || (schema[OpenApiSchemaKeywords.TypeKeyword] is JsonArray schemaType && schemaType.GetValues().Contains("null")); - schema[OpenApiSchemaKeywords.TypeKeyword] = openApiSchema.Type.ToString(); + if (underlyingType != null && MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && + !schemaTypes.HasFlag(JsonSchemaType.Null)) + { + schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString(); + } schema[OpenApiSchemaKeywords.FormatKeyword] = openApiSchema.Format; schema[OpenApiConstants.SchemaId] = createSchemaReferenceId(context.TypeInfo); - schema[OpenApiSchemaKeywords.NullableKeyword] = underlyingType != null; - // Clear out patterns that the underlying JSON schema generator uses to represent - // validations for DateTime, DateTimeOffset, and integers. - schema[OpenApiSchemaKeywords.PatternKeyword] = null; } } @@ -334,14 +340,17 @@ internal static void ApplyParameterInfo(this JsonNode schema, ApiParameterDescri schema.ApplyRouteConstraints(constraints); } - if (parameterDescription.Source is { } bindingSource && SupportsNullableProperty(bindingSource)) + if (parameterDescription.Source is { } bindingSource + && SupportsNullableProperty(bindingSource) + && MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && + schemaTypes.HasFlag(JsonSchemaType.Null)) { - schema[OpenApiSchemaKeywords.NullableKeyword] = false; + schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes & ~JsonSchemaType.Null).ToString(); } // Parameters sourced from the header, query, route, and/or form cannot be nullable based on our binding // rules but can be optional. - static bool SupportsNullableProperty(BindingSource bindingSource) =>bindingSource == BindingSource.Header + static bool SupportsNullableProperty(BindingSource bindingSource) => bindingSource == BindingSource.Header || bindingSource == BindingSource.Query || bindingSource == BindingSource.Path || bindingSource == BindingSource.Form @@ -435,9 +444,11 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, Parameter var nullabilityInfoContext = new NullabilityInfoContext(); var nullabilityInfo = nullabilityInfoContext.Create(parameterInfo); - if (nullabilityInfo.WriteState == NullabilityState.Nullable) + if (nullabilityInfo.WriteState == NullabilityState.Nullable + && MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes + && !schemaTypes.HasFlag(JsonSchemaType.Null)) { - schema[OpenApiSchemaKeywords.NullableKeyword] = true; + schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString(); } } @@ -452,7 +463,54 @@ internal static void ApplyNullabilityContextInfo(this JsonNode schema, JsonPrope // all schema (no type, no format, no constraints). if (propertyInfo.PropertyType != typeof(object) && (propertyInfo.IsGetNullable || propertyInfo.IsSetNullable)) { - schema[OpenApiSchemaKeywords.NullableKeyword] = true; + if (MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && + !schemaTypes.HasFlag(JsonSchemaType.Null)) + { + schema[OpenApiSchemaKeywords.TypeKeyword] = (schemaTypes | JsonSchemaType.Null).ToString(); + } + } + } + + private static JsonSchemaType? MapJsonNodeToSchemaType(JsonNode? jsonNode) + { + if (jsonNode is not JsonArray jsonArray) + { + if (Enum.TryParse(jsonNode?.GetValue(), true, out var openApiSchemaType)) + { + return openApiSchemaType; + } + + return jsonNode is JsonValue jsonValue && jsonValue.TryGetValue(out var identifier) + ? ToSchemaType(identifier) + : null; + } + + JsonSchemaType? schemaType = null; + + foreach (var node in jsonArray) + { + if (node is JsonValue jsonValue && jsonValue.TryGetValue(out var identifier)) + { + var type = ToSchemaType(identifier); + schemaType = schemaType.HasValue ? (schemaType | type) : type; + } + } + + return schemaType; + + static JsonSchemaType ToSchemaType(string identifier) + { + return identifier.ToLowerInvariant() switch + { + "null" => JsonSchemaType.Null, + "boolean" => JsonSchemaType.Boolean, + "integer" => JsonSchemaType.Integer, + "number" => JsonSchemaType.Number, + "string" => JsonSchemaType.String, + "array" => JsonSchemaType.Array, + "object" => JsonSchemaType.Object, + _ => throw new InvalidOperationException($"Unknown schema type: {identifier}"), + }; } } } diff --git a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs index b16171bc27cb..cb3b6b26abfc 100644 --- a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs +++ b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models.References; namespace Microsoft.AspNetCore.OpenApi; @@ -9,17 +10,17 @@ namespace Microsoft.AspNetCore.OpenApi; internal static class OpenApiDocumentExtensions { /// - /// Registers a into the top-level components store on the + /// Registers a into the top-level components store on the /// and returns a resolvable reference to it. /// /// The to register the schema onto. /// The ID that serves as the key for the schema in the schema store. - /// The to register into the document. - /// An with a reference to the stored schema. - public static OpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument document, string schemaId, OpenApiSchema schema) + /// The to register into the document. + /// An with a reference to the stored schema. + public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument document, string schemaId, IOpenApiSchema schema) { document.Components ??= new(); - document.Components.Schemas ??= new Dictionary(); + document.Components.Schemas ??= new Dictionary(); document.Components.Schemas[schemaId] = schema; document.Workspace ??= new(); var location = document.BaseUri + "/components/schemas/" + schemaId; diff --git a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs index 29e17c2f1363..cea73303ef25 100644 --- a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs +++ b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs @@ -8,6 +8,7 @@ using System.Text.Json.Serialization; using Microsoft.AspNetCore.OpenApi; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using OpenApiConstants = Microsoft.AspNetCore.OpenApi.OpenApiConstants; internal sealed partial class OpenApiJsonSchema @@ -220,10 +221,6 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName, var valueConverter = (JsonConverter)options.GetTypeInfo(typeof(OpenApiJsonSchema)).Converter; schema.Items = valueConverter.Read(ref reader, typeof(OpenApiJsonSchema), options)?.Schema; break; - case OpenApiSchemaKeywords.NullableKeyword: - reader.Read(); - schema.Nullable = reader.GetBoolean(); - break; case OpenApiSchemaKeywords.DescriptionKeyword: reader.Read(); schema.Description = reader.GetString(); @@ -274,7 +271,7 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName, case OpenApiSchemaKeywords.PropertiesKeyword: reader.Read(); var props = ReadDictionary(ref reader); - schema.Properties = props?.ToDictionary(p => p.Key, p => p.Value.Schema); + schema.Properties = props?.ToDictionary(p => p.Key, p => p.Value.Schema as IOpenApiSchema); break; case OpenApiSchemaKeywords.AdditionalPropertiesKeyword: reader.Read(); @@ -290,7 +287,7 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName, reader.Read(); schema.Type = JsonSchemaType.Object; var schemas = ReadList(ref reader); - schema.AnyOf = schemas?.Select(s => s.Schema).ToList(); + schema.AnyOf = schemas?.Select(s => s.Schema as IOpenApiSchema).ToList(); break; case OpenApiSchemaKeywords.DiscriminatorKeyword: reader.Read(); @@ -322,7 +319,8 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName, break; case OpenApiSchemaKeywords.RefKeyword: reader.Read(); - schema.Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = reader.GetString() }; + schema.Annotations ??= new Dictionary(); + schema.Annotations[OpenApiConstants.RefId] = reader.GetString(); break; default: reader.Skip(); diff --git a/src/OpenApi/src/Schemas/OpenApiSchemaKeywords.cs b/src/OpenApi/src/Schemas/OpenApiSchemaKeywords.cs index 37e4121462b9..255cfae73c1c 100644 --- a/src/OpenApi/src/Schemas/OpenApiSchemaKeywords.cs +++ b/src/OpenApi/src/Schemas/OpenApiSchemaKeywords.cs @@ -12,7 +12,6 @@ internal class OpenApiSchemaKeywords public const string AnyOfKeyword = "anyOf"; public const string EnumKeyword = "enum"; public const string DefaultKeyword = "default"; - public const string NullableKeyword = "nullable"; public const string DescriptionKeyword = "description"; public const string DiscriminatorKeyword = "discriminatorName"; public const string DiscriminatorMappingKeyword = "discriminatorMapping"; diff --git a/src/OpenApi/src/Services/OpenApiConstants.cs b/src/OpenApi/src/Services/OpenApiConstants.cs index e6d99096dc45..8e5d29824514 100644 --- a/src/OpenApi/src/Services/OpenApiConstants.cs +++ b/src/OpenApi/src/Services/OpenApiConstants.cs @@ -12,6 +12,7 @@ internal static class OpenApiConstants internal const string DefaultOpenApiRoute = "/openapi/{documentName}.json"; internal const string DescriptionId = "x-aspnetcore-id"; 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 diff --git a/src/OpenApi/src/Services/OpenApiDocumentService.cs b/src/OpenApi/src/Services/OpenApiDocumentService.cs index 08cc50a5f953..45fa4fa3f9f0 100644 --- a/src/OpenApi/src/Services/OpenApiDocumentService.cs +++ b/src/OpenApi/src/Services/OpenApiDocumentService.cs @@ -26,6 +26,7 @@ using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models.References; namespace Microsoft.AspNetCore.OpenApi; @@ -89,7 +90,7 @@ public async Task GetOpenApiDocumentAsync(IServiceProvider scop document.Workspace.RegisterComponents(document); if (document.Components?.Schemas is not null) { - document.Components.Schemas = new SortedDictionary(document.Components.Schemas); + document.Components.Schemas = new SortedDictionary(document.Components.Schemas); } return document; } @@ -399,14 +400,14 @@ private async Task GetResponseAsync( return response; } - private async Task?> GetParametersAsync( + private async Task?> GetParametersAsync( OpenApiDocument document, ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken) { - List? parameters = null; + List? parameters = null; foreach (var parameter in description.ParameterDescriptions) { if (ShouldIgnoreParameter(parameter)) @@ -513,7 +514,7 @@ private async Task GetFormRequestBody( Content = new Dictionary() }; - var schema = new OpenApiSchema { Type = JsonSchemaType.Object, Properties = new Dictionary() }; + IOpenApiSchema schema = new OpenApiSchema { Type = JsonSchemaType.Object, Properties = new Dictionary() }; // Group form parameters by their name because MVC explodes form parameters that are bound from the // same model instance into separate ApiParameterDescriptions in ApiExplorer, while minimal APIs does not. // @@ -547,7 +548,7 @@ private async Task GetFormRequestBody( schema.AllOf.Add(new OpenApiSchema { Type = JsonSchemaType.Object, - Properties = new Dictionary + Properties = new Dictionary { [description.Name] = parameterSchema } @@ -583,7 +584,7 @@ private async Task GetFormRequestBody( schema.AllOf.Add(new OpenApiSchema { Type = JsonSchemaType.Object, - Properties = new Dictionary + Properties = new Dictionary { [description.Name] = parameterSchema } @@ -611,7 +612,7 @@ private async Task GetFormRequestBody( { if (hasMultipleFormParameters) { - var propertySchema = new OpenApiSchema { Type = JsonSchemaType.Object, Properties = new Dictionary() }; + var propertySchema = new OpenApiSchema { Type = JsonSchemaType.Object, Properties = new Dictionary() }; foreach (var description in parameter) { propertySchema.Properties[description.Name] = await _componentService.GetOrCreateSchemaAsync(document, description.Type, scopedServiceProvider, schemaTransformers, description, cancellationToken: cancellationToken); @@ -706,7 +707,7 @@ private static Type GetTargetType(ApiDescription description, ApiParameterDescri 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. + // Enums are exempt because we want to set the IOpenApiSchema.Enum field when feasible. // parameter.Type = typeof(TEnum), typeof(TypeWithTryParse) // parameter.ModelMetadata.Type = typeof(string) var hasTryParse = bindingMetadata?.HasTryParse == true && parameterType is not null && !parameterType.IsEnum; diff --git a/src/OpenApi/src/Services/OpenApiGenerator.cs b/src/OpenApi/src/Services/OpenApiGenerator.cs index ce76cfb88694..a96ab12c5b85 100644 --- a/src/OpenApi/src/Services/OpenApiGenerator.cs +++ b/src/OpenApi/src/Services/OpenApiGenerator.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Internal; using Microsoft.Extensions.Primitives; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models.References; namespace Microsoft.AspNetCore.OpenApi; @@ -363,10 +364,10 @@ private List GetOperationTags(MethodInfo methodInfo, Endpoi return [new(controllerName, document)]; } - private List GetOpenApiParameters(MethodInfo methodInfo, RoutePattern pattern, bool disableInferredBody) + private List GetOpenApiParameters(MethodInfo methodInfo, RoutePattern pattern, bool disableInferredBody) { var parameters = PropertyAsParameterInfo.Flatten(methodInfo.GetParameters(), ParameterBindingMethodCache.Instance); - var openApiParameters = new List(); + var openApiParameters = new List(); foreach (var parameter in parameters) { diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 986c9252a636..0714e77fee35 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -17,6 +17,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; namespace Microsoft.AspNetCore.OpenApi; @@ -94,14 +96,6 @@ internal sealed class OpenApiSchemaService( schema.MapPolymorphismOptionsToDiscriminator(context, createSchemaReferenceId); if (context.PropertyInfo is { } jsonPropertyInfo) { - // Short-circuit STJ's handling of nested properties, which uses a reference to the - // properties type schema with a schema that uses a document level reference. - // For example, if the property is a `public NestedTyped Nested { get; set; }` property, - // "nested": "#/properties/nested" becomes "nested": "#/components/schemas/NestedType" - if (jsonPropertyInfo.PropertyType == jsonPropertyInfo.DeclaringType) - { - return new JsonObject { [OpenApiSchemaKeywords.RefKeyword] = createSchemaReferenceId(context.TypeInfo) }; - } schema.ApplyNullabilityContextInfo(jsonPropertyInfo); } if (context.PropertyInfo is { AttributeProvider: { } attributeProvider }) @@ -124,7 +118,7 @@ internal sealed class OpenApiSchemaService( } }; - internal async Task GetOrCreateSchemaAsync(OpenApiDocument document, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription? parameterDescription = null, CancellationToken cancellationToken = default) + internal async Task GetOrCreateSchemaAsync(OpenApiDocument document, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription? parameterDescription = null, CancellationToken cancellationToken = default) { var key = parameterDescription?.ParameterDescriptor is IParameterInfoParameterDescriptor parameterInfoDescription && parameterDescription.ModelMetadata.PropertyName is null @@ -143,8 +137,14 @@ internal async Task GetOrCreateSchemaAsync(OpenApiDocument docume return ResolveReferenceForSchema(document, schema); } - internal static OpenApiSchema ResolveReferenceForSchema(OpenApiDocument document, OpenApiSchema schema, string? baseSchemaId = null) + internal static IOpenApiSchema ResolveReferenceForSchema(OpenApiDocument document, IOpenApiSchema inputSchema, string? baseSchemaId = null) { + var schema = inputSchema is OpenApiSchemaReference schemaReference + ? schemaReference.Target + : inputSchema is OpenApiSchema directSchema + ? directSchema + : throw new InvalidOperationException("The input schema must be an OpenApiSchema or OpenApiSchemaReference."); + if (schema.Annotations is not null && schema.Annotations.TryGetValue(OpenApiConstants.SchemaId, out var resolvedBaseSchemaId)) { @@ -200,8 +200,8 @@ internal static OpenApiSchema ResolveReferenceForSchema(OpenApiDocument document // the `#` ID is generated by the exporter since it has no base document to baseline against. In this // case we we want to replace the reference ID with the schema ID that was generated by the // `CreateSchemaReferenceId` method in the OpenApiSchemaService. - if (schema.Reference is { Type: ReferenceType.Schema, Id: "#" } && - schema.Annotations is not null && + if (schema.Annotations is not null && + schema.Annotations.ContainsKey(OpenApiConstants.RefId) && schema.Annotations.TryGetValue(OpenApiConstants.SchemaId, out var schemaId) && schemaId is string schemaIdString) { @@ -212,8 +212,8 @@ schema.Annotations is not null && // we don't want to replace the top-level inline schema with a reference to itself. We want to replace // inline schemas to reference schemas for all schemas referenced in the top-level schema though (such as // `allOf`, `oneOf`, `anyOf`, `items`, `properties`, etc.) which is why `isTopLevel` is only set once. - if (schema.Reference is null && - schema.Annotations is not null && + if (schema.Annotations is not null && + !schema.Annotations.ContainsKey(OpenApiConstants.RefId) && schema.Annotations.TryGetValue(OpenApiConstants.SchemaId, out var referenceId) && referenceId is string referenceIdString) { @@ -222,14 +222,14 @@ schema.Annotations is not null && : referenceIdString; if (targetReferenceId is not null) { - schema = document.AddOpenApiSchemaByReference(targetReferenceId, schema); + return document.AddOpenApiSchemaByReference(targetReferenceId, schema); } } return schema; } - internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription? parameterDescription = null, CancellationToken cancellationToken = default) + internal async Task ApplySchemaTransformersAsync(IOpenApiSchema schema, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription? parameterDescription = null, CancellationToken cancellationToken = default) { if (schemaTransformers.Length == 0) { @@ -252,7 +252,7 @@ internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type } } - private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema, + private async Task InnerApplySchemaTransformersAsync(IOpenApiSchema inputSchema, JsonTypeInfo jsonTypeInfo, JsonPropertyInfo? jsonPropertyInfo, OpenApiSchemaTransformerContext context, @@ -260,6 +260,11 @@ private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema, CancellationToken cancellationToken = default) { context.UpdateJsonTypeInfo(jsonTypeInfo, jsonPropertyInfo); + var schema = inputSchema is OpenApiSchemaReference schemaReference + ? schemaReference.Target + : inputSchema is OpenApiSchema directSchema + ? directSchema + : throw new InvalidOperationException("The input schema must be an OpenApiSchema or OpenApiSchemaReference."); await transformer.TransformAsync(schema, context, cancellationToken); // Only apply transformers on polymorphic schemas where we can resolve the derived diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 3e1be752293a..3b7a14c9aae6 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Any; [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] @@ -237,12 +238,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); if (operationParameter is not null) { - operationParameter.Description = parameterComment.Description; + var targetOperationParameter = operationParameter is OpenApiParameterReference reference + ? reference.Target + : (OpenApiParameter)operationParameter; + targetOperationParameter.Description = parameterComment.Description; if (parameterComment.Example is { } jsonString) { - operationParameter.Example = jsonString.Parse(); + targetOperationParameter.Example = jsonString.Parse(); } - operationParameter.Deprecated = parameterComment.Deprecated; + targetOperationParameter.Deprecated = parameterComment.Deprecated; } else { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 14d8bbcacb55..aa9137dd68b8 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Any; [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] @@ -334,12 +335,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); if (operationParameter is not null) { - operationParameter.Description = parameterComment.Description; + var targetOperationParameter = operationParameter is OpenApiParameterReference reference + ? reference.Target + : (OpenApiParameter)operationParameter; + targetOperationParameter.Description = parameterComment.Description; if (parameterComment.Example is { } jsonString) { - operationParameter.Example = jsonString.Parse(); + targetOperationParameter.Example = jsonString.Parse(); } - operationParameter.Deprecated = parameterComment.Deprecated; + targetOperationParameter.Deprecated = parameterComment.Deprecated; } else { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 54e370279a60..8e06da9831f3 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Any; [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] @@ -241,12 +242,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); if (operationParameter is not null) { - operationParameter.Description = parameterComment.Description; + var targetOperationParameter = operationParameter is OpenApiParameterReference reference + ? reference.Target + : (OpenApiParameter)operationParameter; + targetOperationParameter.Description = parameterComment.Description; if (parameterComment.Example is { } jsonString) { - operationParameter.Example = jsonString.Parse(); + targetOperationParameter.Example = jsonString.Parse(); } - operationParameter.Deprecated = parameterComment.Deprecated; + targetOperationParameter.Deprecated = parameterComment.Deprecated; } else { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 0973c3d23999..397466f8fa7e 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Any; [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] @@ -249,12 +250,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); if (operationParameter is not null) { - operationParameter.Description = parameterComment.Description; + var targetOperationParameter = operationParameter is OpenApiParameterReference reference + ? reference.Target + : (OpenApiParameter)operationParameter; + targetOperationParameter.Description = parameterComment.Description; if (parameterComment.Example is { } jsonString) { - operationParameter.Example = jsonString.Parse(); + targetOperationParameter.Example = jsonString.Parse(); } - operationParameter.Deprecated = parameterComment.Deprecated; + targetOperationParameter.Deprecated = parameterComment.Deprecated; } else { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 8abf7d226a17..ae4701bc970b 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Any; [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] @@ -265,12 +266,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); if (operationParameter is not null) { - operationParameter.Description = parameterComment.Description; + var targetOperationParameter = operationParameter is OpenApiParameterReference reference + ? reference.Target + : (OpenApiParameter)operationParameter; + targetOperationParameter.Description = parameterComment.Description; if (parameterComment.Example is { } jsonString) { - operationParameter.Example = jsonString.Parse(); + targetOperationParameter.Example = jsonString.Parse(); } - operationParameter.Deprecated = parameterComment.Deprecated; + targetOperationParameter.Deprecated = parameterComment.Deprecated; } else { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiRouteHandlerBuilderExtensionTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiRouteHandlerBuilderExtensionTests.cs index 9e401fdbe248..f660f6557121 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiRouteHandlerBuilderExtensionTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiRouteHandlerBuilderExtensionTests.cs @@ -74,7 +74,7 @@ public void WithOpenApi_CanSetSchemaInOperationWithOverride() _ = builder.MapDelete("/{id}", GetString) .WithOpenApi(operation => new(operation) { - Parameters = new List() { new() { Schema = new() { Type = JsonSchemaType.Number } } } + Parameters = [new OpenApiParameter() { Schema = new OpenApiSchema() { Type = JsonSchemaType.Number } }] }); var dataSource = GetBuilderEndpointDataSource(builder); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs index 27c0d8bd452d..86239cbc84ee 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs @@ -5,8 +5,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.OpenApi; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; public class CreateSchemaReferenceIdTests : OpenApiDocumentServiceTestBase { @@ -78,7 +78,7 @@ await VerifyOpenApiDocument(builder, options, document => var content = Assert.Single(requestBody.Content); Assert.Equal("application/json", content.Key); Assert.NotNull(content.Value.Schema); - Assert.Equal("TodoSchema", content.Value.Schema.Reference.Id); + Assert.Equal("TodoSchema", ((OpenApiSchemaReference)content.Value.Schema).Reference.Id); var schema = content.Value.Schema; Assert.Equal(JsonSchemaType.Object, schema.Type); Assert.Collection(schema.Properties, @@ -90,7 +90,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -129,7 +129,7 @@ await VerifyOpenApiDocument(builder, options, document => Assert.NotNull(content.Value.Schema); // Assert that no reference was created and the schema is inlined var schema = content.Value.Schema; - Assert.Null(schema.Reference); + Assert.IsType(schema); Assert.Equal(JsonSchemaType.Object, schema.Type); Assert.Collection(schema.Properties, property => @@ -140,7 +140,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -187,7 +187,7 @@ await VerifyOpenApiDocument(builder, options, document => Assert.Equal("application/json", content.Key); Assert.NotNull(content.Value.Schema); var schema = content.Value.Schema; - Assert.Null(schema.Reference); + Assert.IsNotType(schema); // Assert that a reference was created for the TodoWithDueDate type Assert.NotNull(response); @@ -195,8 +195,8 @@ await VerifyOpenApiDocument(builder, options, document => Assert.Equal("application/json", responseContent.Key); Assert.NotNull(responseContent.Value.Schema); var responseSchema = responseContent.Value.Schema; - Assert.NotNull(responseSchema.Reference); - Assert.Equal("TodoWithDueDate", responseSchema.Reference.Id); + Assert.IsType(responseSchema); + Assert.Equal("TodoWithDueDate", ((OpenApiSchemaReference)responseSchema).Reference.Id); }); } @@ -230,7 +230,7 @@ await VerifyOpenApiDocument(builder, options, document => Assert.Equal("application/json", content.Key); Assert.NotNull(content.Value.Schema); var schema = content.Value.Schema; - Assert.NotNull(schema.Reference); + Assert.IsType(schema); // Assert that a reference was created for the TodoWithDueDate type Assert.NotNull(response); @@ -238,10 +238,10 @@ await VerifyOpenApiDocument(builder, options, document => Assert.Equal("application/json", responseContent.Key); Assert.NotNull(responseContent.Value.Schema); var responseSchema = responseContent.Value.Schema; - Assert.NotNull(responseSchema.Reference); + Assert.IsType(responseSchema); // Assert that the reference IDs are not the same (have been deduped) - Assert.NotEqual(schema.Reference.Id, responseSchema.Reference.Id); + Assert.NotEqual(((OpenApiSchemaReference)schema).Reference.Id, ((OpenApiSchemaReference)responseSchema).Reference.Id); // Assert that the referenced schemas are correct var effectiveResponseSchema = responseSchema; @@ -250,7 +250,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("dueDate", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("date-time", property.Value.Format); }, property => @@ -261,7 +261,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -271,7 +271,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("createdAt", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("date-time", property.Value.Format); }); @@ -286,7 +286,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -296,7 +296,7 @@ await VerifyOpenApiDocument(builder, options, document => property => { Assert.Equal("createdAt", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("date-time", property.Value.Format); }); }); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Operations.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Operations.cs index 88ec906628b7..bbca1d365a02 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Operations.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Operations.cs @@ -182,62 +182,6 @@ await VerifyOpenApiDocument(builder, document => }); } - [Fact] - public async Task GetOpenApiOperation_EditingReferenceInOperationThrowsException() - { - // Arrange - var builder = CreateBuilder(); - - // Act - builder.MapGet("/api/todos", () => { }).WithTags(["todos"]); - var options = new OpenApiOptions(); - options.AddOperationTransformer((operation, context, cancellationToken) => - { - foreach (var tag in operation.Tags) - { - tag.Name = "newTag"; // Should throw exception - } - return Task.CompletedTask; - }); - - // Assert - var exception = await Assert.ThrowsAsync(() => VerifyOpenApiDocument(builder, options, _ => { })); - Assert.Equal("Setting the value from the reference is not supported, use the target property instead.", exception.Message); - } - - [Fact] - public async Task GetOpenApiOperation_EditingTargetTagInOperationWorks() - { - // Arrange - var builder = CreateBuilder(); - - // Act - builder.MapGet("/api/todos", () => { }).WithTags(["todos"]); - var options = new OpenApiOptions(); - options.AddOperationTransformer((operation, context, cancellationToken) => - { - foreach (var tag in operation.Tags) - { - tag.Target.Name = "newTag"; - } - return Task.CompletedTask; - }); - - // Assert - await VerifyOpenApiDocument(builder, options, document => - { - var operation = document.Paths["/api/todos"].Operations[OperationType.Get]; - Assert.Collection(operation.Tags, tag => - { - Assert.Equal("newTag", tag.Name); - }); - Assert.Collection(document.Tags, tag => - { - Assert.Equal("newTag", tag.Name); - }); - }); - } - [Fact] public async Task GetOpenApiOperation_CapturesEndpointNameAsOperationId() { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs index 01a6445bd632..032720da5cc0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs @@ -434,7 +434,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -591,7 +591,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -616,7 +616,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("message", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); }); } @@ -756,7 +756,7 @@ await VerifyOpenApiDocument(action, document => Assert.All(item.Schema.Properties, property => { - Assert.False(property.Value.Nullable); + Assert.False(property.Value.Type?.HasFlag(JsonSchemaType.Null)); }); } }); @@ -836,17 +836,17 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("description", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("resume", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("binary", property.Value.Format); }); } @@ -974,7 +974,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -1038,7 +1038,7 @@ await VerifyOpenApiDocument(action, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -1048,7 +1048,7 @@ await VerifyOpenApiDocument(action, document => property => { Assert.Equal("createdAt", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("date-time", property.Value.Format); }); }, @@ -1057,7 +1057,7 @@ await VerifyOpenApiDocument(action, document => Assert.Collection(allOfItem.Properties, property => { Assert.Equal("formFile", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("binary", property.Value.Format); }); }, @@ -1066,7 +1066,7 @@ await VerifyOpenApiDocument(action, document => Assert.Collection(allOfItem.Properties, property => { Assert.Equal("guid", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("uuid", property.Value.Format); }); }); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Responses.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Responses.cs index 6b208b51486d..61ef643260b6 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Responses.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Responses.cs @@ -240,7 +240,7 @@ await VerifyOpenApiDocument(builder, document => }, property => { Assert.Equal("message", property.Key); - Assert.Equal( JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); }); } @@ -274,7 +274,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("message", property.Key); - Assert.Equal( JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); // Generates the 200 status code response with the `Todo` response type. var okResponse = operation.Responses["200"]; @@ -291,7 +291,7 @@ await VerifyOpenApiDocument(builder, document => }, property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("completed", property.Key); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs index 15076da179ad..00aeaef2e6d5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs @@ -3,10 +3,12 @@ using System.Reflection; using System.Text; +using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Json; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; @@ -84,7 +86,11 @@ internal static OpenApiDocumentService CreateDocumentService(IEndpointRouteBuild var openApiOptions = new Mock>(); openApiOptions.Setup(o => o.Get(It.IsAny())).Returns(new OpenApiOptions()); - var schemaService = new OpenApiSchemaService("Test", Options.Create(new Microsoft.AspNetCore.Http.Json.JsonOptions()), openApiOptions.Object); + var jsonOptions = new Microsoft.AspNetCore.Http.Json.JsonOptions(); + // Set strict number handling by default to make integer type checks more straightforward + jsonOptions.SerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.Strict; + + var schemaService = new OpenApiSchemaService("Test", Options.Create(jsonOptions), openApiOptions.Object); ((TestServiceProvider)builder.ServiceProvider).TestSchemaService = schemaService; var documentService = new OpenApiDocumentService("Test", apiDescriptionGroupCollectionProvider, hostEnvironment, openApiOptions.Object, builder.ServiceProvider, new OpenApiTestServer()); ((TestServiceProvider)builder.ServiceProvider).TestDocumentService = documentService; @@ -125,7 +131,10 @@ internal static OpenApiDocumentService CreateDocumentService(IEndpointRouteBuild provider.OnProvidersExecuted(context); var apiDescriptionGroupCollectionProvider = CreateApiDescriptionGroupCollectionProvider(context.Results); - var jsonOptions = builder.ServiceProvider.GetService>() ?? Options.Create(new Microsoft.AspNetCore.Http.Json.JsonOptions()); + var defaultJsonOptions = new Microsoft.AspNetCore.Http.Json.JsonOptions(); + // Set strict number handling by default to make integer type checks more straightforward + defaultJsonOptions.SerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.Strict; + var jsonOptions = builder.ServiceProvider.GetService>() ?? Options.Create(defaultJsonOptions); var schemaService = new OpenApiSchemaService("Test", jsonOptions, options.Object); ((TestServiceProvider)builder.ServiceProvider).TestSchemaService = schemaService; @@ -158,8 +167,13 @@ private static EndpointMetadataApiDescriptionProvider CreateEndpointMetadataApiD internal static TestEndpointRouteBuilder CreateBuilder(IServiceCollection serviceCollection = null) { + serviceCollection ??= new ServiceCollection(); + serviceCollection.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.Strict; + }); var serviceProvider = new TestServiceProvider(); - serviceProvider.SetInternalServiceProvider(serviceCollection ?? new ServiceCollection()); + serviceProvider.SetInternalServiceProvider(serviceCollection); return new TestEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs index 2f8fddb13eb6..829f906f4b41 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -14,6 +13,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; public partial class OpenApiSchemaServiceTests : OpenApiDocumentServiceTestBase { @@ -74,7 +74,6 @@ await VerifyOpenApiDocument(builder, document => var parameter = Assert.Single(operation.Parameters); Assert.Equal(schemaType, parameter.Schema.Type); Assert.Equal(schemaFormat, parameter.Schema.Format); - Assert.False(parameter.Schema.Nullable); }); } @@ -361,7 +360,8 @@ await VerifyOpenApiDocument(builder, document => { var operation = document.Paths["/api/{id}"].Operations[OperationType.Get]; var parameter = Assert.Single(operation.Parameters); - verifySchema(parameter.Schema); + var schema = Assert.IsType(parameter.Schema); + verifySchema(schema); }); } @@ -390,7 +390,8 @@ await VerifyOpenApiDocument(builder, document => { var operation = document.Paths["/api/{id}"].Operations[OperationType.Get]; var parameter = Assert.Single(operation.Parameters); - verifySchema(parameter.Schema); + var schema = Assert.IsType(parameter.Schema); + verifySchema(schema); }); } @@ -418,7 +419,8 @@ await VerifyOpenApiDocument(builder, document => { var operation = document.Paths["/api/{id}"].Operations[OperationType.Get]; var parameter = Assert.Single(operation.Parameters); - verifySchema(parameter.Schema); + var schema = Assert.IsType(parameter.Schema); + verifySchema(schema); }); } @@ -481,9 +483,9 @@ await VerifyOpenApiDocument(builder, document => // When the element type is not nullable (int[] ints), the binding // will produce [1, 2, 0, 4] Assert.Equal(JsonSchemaType.Array, parameter.Schema.Type); - Assert.Equal(JsonSchemaType.Array, parameter.Schema.Type); - Assert.Equal(innerSchemaType, parameter.Schema.Items.Type); - Assert.Equal(isNullable, parameter.Schema.Items.Nullable); + + Assert.True(parameter.Schema.Items.Type?.HasFlag(innerSchemaType)); + Assert.Equal(isNullable, parameter.Schema.Items.Type?.HasFlag(JsonSchemaType.Null)); }); } @@ -619,8 +621,7 @@ static void AssertOpenApiDocument(OpenApiDocument document) var operation = document.Paths["/api/with-enum"].Operations[OperationType.Get]; var parameter = Assert.Single(operation.Parameters); var response = Assert.Single(operation.Responses).Value.Content["application/json"].Schema; - Assert.NotNull(parameter.Schema.Reference); - Assert.Equal(parameter.Schema.Reference.Id, response.Reference.Id); + Assert.Equal(((OpenApiSchemaReference)parameter.Schema).Reference.Id, ((OpenApiSchemaReference)response).Reference.Id); var schema = parameter.Schema; Assert.Collection(schema.Enum, value => @@ -695,7 +696,7 @@ static void AssertOpenApiDocument(OpenApiDocument document) Assert.Collection(schema.Properties, property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PolymorphicSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PolymorphicSchemas.cs index 53dbe46ca2c9..8002c603029c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PolymorphicSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PolymorphicSchemas.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; public partial class OpenApiSchemaServiceTests : OpenApiDocumentServiceTestBase { @@ -150,9 +151,9 @@ await VerifyOpenApiDocument(builder, document => // property associated with them. Assert.Null(schema.Discriminator); Assert.Collection(schema.AnyOf, - schema => Assert.Equal("ColorPaintColor", schema.Reference.Id), - schema => Assert.Equal("ColorFabricColor", schema.Reference.Id), - schema => Assert.Equal("ColorBase", schema.Reference.Id)); + schema => Assert.Equal("ColorPaintColor", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("ColorFabricColor", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("ColorBase", ((OpenApiSchemaReference)schema).Reference.Id)); // Assert schema with discriminator = "paint" has been inserted into the components Assert.True(document.Components.Schemas.TryGetValue("ColorPaintColor", out var paintSchema)); Assert.Contains("$type", paintSchema.Properties.Keys); @@ -202,9 +203,9 @@ await VerifyOpenApiDocument(builder, document => // type. In this scenario, we check that the base class is not included in the `anyOf` // schema. Assert.Collection(schema.AnyOf, - schema => Assert.Equal("PetCat", schema.Reference.Id), - schema => Assert.Equal("PetDog", schema.Reference.Id), - schema => Assert.Equal("PetPet", schema.Reference.Id)); + schema => Assert.Equal("PetCat", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("PetDog", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("PetPet", ((OpenApiSchemaReference)schema).Reference.Id)); // Assert schema with discriminator = "dog" has been inserted into the components Assert.True(document.Components.Schemas.TryGetValue("PetDog", out var dogSchema)); Assert.Contains(schema.Discriminator.PropertyName, dogSchema.Properties.Keys); @@ -242,9 +243,9 @@ await VerifyOpenApiDocument(builder, document => // property associated with them. Assert.Null(schema.Discriminator); Assert.Collection(schema.AnyOf, - schema => Assert.Equal("OrganismAnimal", schema.Reference.Id), - schema => Assert.Equal("OrganismPlant", schema.Reference.Id), - schema => Assert.Equal("OrganismBase", schema.Reference.Id)); + schema => Assert.Equal("OrganismAnimal", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("OrganismPlant", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("OrganismBase", ((OpenApiSchemaReference)schema).Reference.Id)); // Assert that schemas without discriminators have been inserted into the components Assert.True(document.Components.Schemas.TryGetValue("OrganismAnimal", out var animalSchema)); Assert.DoesNotContain("$type", animalSchema.Properties.Keys); @@ -271,7 +272,7 @@ await VerifyOpenApiDocument(builder, document => Assert.NotNull(operation.RequestBody); var requestBody = operation.RequestBody.Content; Assert.True(requestBody.TryGetValue("application/json", out var mediaType)); - Assert.Equal("Employee", mediaType.Schema.Reference.Id); + Assert.Equal("Employee", ((OpenApiSchemaReference)mediaType.Schema).Reference.Id); var schema = mediaType.Schema; // Assert that discriminator mappings are configured correctly for type. Assert.Equal("$type", schema.Discriminator.PropertyName); @@ -285,15 +286,15 @@ await VerifyOpenApiDocument(builder, document => ); // Assert that anyOf schemas use the correct reference IDs. Assert.Collection(schema.AnyOf, - schema => Assert.Equal("EmployeeManager", schema.Reference.Id), - schema => Assert.Equal("EmployeeEmployee", schema.Reference.Id)); + schema => Assert.Equal("EmployeeManager", ((OpenApiSchemaReference)schema).Reference.Id), + schema => Assert.Equal("EmployeeEmployee", ((OpenApiSchemaReference)schema).Reference.Id)); // Assert that schemas without discriminators have been inserted into the components Assert.True(document.Components.Schemas.TryGetValue("EmployeeManager", out var managerSchema)); Assert.Equal("manager", managerSchema.Properties[schema.Discriminator.PropertyName].Enum.First().GetValue()); Assert.True(document.Components.Schemas.TryGetValue("EmployeeEmployee", out var employeeSchema)); Assert.Equal("employee", employeeSchema.Properties[schema.Discriminator.PropertyName].Enum.First().GetValue()); // Assert that the schema has a correct self-reference to the base-type. This points to the schema that contains the discriminator. - Assert.Equal("Employee", employeeSchema.Properties["manager"].Reference.Id); + Assert.Equal("Employee", ((OpenApiSchemaReference)employeeSchema.Properties["manager"]).Reference.Id); }); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs index 03744160a9e9..bbd87f3afaa4 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; public partial class OpenApiSchemaServiceTests : OpenApiDocumentServiceTestBase { @@ -44,7 +45,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -103,7 +104,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal(5, property.Value.MinLength); Assert.Null(property.Value.Default); }, @@ -144,7 +145,7 @@ await VerifyOpenApiDocument(builder, document => static OpenApiRequestBody GetRequestBodyForPath(OpenApiDocument document, string path) { var operation = document.Paths[path].Operations[OperationType.Post]; - return operation.RequestBody; + return operation.RequestBody as OpenApiRequestBody; } } @@ -213,17 +214,17 @@ await VerifyOpenApiDocument(builder, document => var operation = document.Paths[$"/proposal"].Operations[OperationType.Post]; var requestBody = operation.RequestBody; var schema = requestBody.Content["application/json"].Schema; - Assert.Equal("Proposal", schema.Reference.Id); + Assert.Equal("Proposal", ((OpenApiSchemaReference)schema).Reference.Id); var effectiveSchema = schema; Assert.Collection(effectiveSchema.Properties, property => { Assert.Equal("proposalElement", property.Key); - Assert.Equal("Proposal", property.Value.Reference.Id); + Assert.Equal("Proposal", ((OpenApiSchemaReference)property.Value).Reference.Id); }, property => { Assert.Equal("stream", property.Key); var targetSchema = property.Value; - Assert.Equal(JsonSchemaType.String, targetSchema.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, targetSchema.Type); Assert.Equal("binary", targetSchema.Format); }); }); @@ -254,7 +255,7 @@ await VerifyOpenApiDocument(builder, document => var enumerableTodoSchema = enumerableTodo.RequestBody.Content["application/json"].Schema; var arrayTodoSchema = arrayTodo.RequestBody.Content["application/json"].Schema; // Assert that both IEnumerable and Todo[] have items that map to the same schema - Assert.Equal(enumerableTodoSchema.Items.Reference.Id, arrayTodoSchema.Items.Reference.Id); + Assert.Equal(((OpenApiSchemaReference)enumerableTodoSchema.Items).Reference.Id, ((OpenApiSchemaReference)arrayTodoSchema.Items).Reference.Id); // Assert all types materialize as arrays Assert.Equal(JsonSchemaType.Array, enumerableTodoSchema.Type); Assert.Equal(JsonSchemaType.Array, arrayTodoSchema.Type); @@ -275,7 +276,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -327,7 +328,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("make", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); }); } @@ -361,7 +362,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal("The title of the todo item.", property.Value.Description); }, property => @@ -418,34 +419,29 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("nullableInt", property.Key); - Assert.Equal( JsonSchemaType.Integer, property.Value.Type); - Assert.True(property.Value.Nullable); + Assert.Equal(JsonSchemaType.Integer | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("nullableString", property.Key); - Assert.Equal( JsonSchemaType.String, property.Value.Type); - Assert.True(property.Value.Nullable); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("nullableBool", property.Key); - Assert.Equal( JsonSchemaType.Boolean, property.Value.Type); - Assert.True(property.Value.Nullable); + Assert.Equal(JsonSchemaType.Boolean | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("nullableDateTime", property.Key); - Assert.Equal( JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null | JsonSchemaType.Null, property.Value.Type); Assert.Equal("date-time", property.Value.Format); - Assert.True(property.Value.Nullable); }, property => { Assert.Equal("nullableUri", property.Key); - Assert.Equal( JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null | JsonSchemaType.Null, property.Value.Type); Assert.Equal("uri", property.Value.Format); - Assert.True(property.Value.Nullable); }); }); } @@ -465,18 +461,18 @@ await VerifyOpenApiDocument(builder, document => var operation = document.Paths["/api"].Operations[OperationType.Post]; var requestBody = operation.RequestBody; var content = Assert.Single(requestBody.Content); - Assert.Equal("NestedType", content.Value.Schema.Reference.Id); + Assert.Equal("NestedType", ((OpenApiSchemaReference)content.Value.Schema).Reference.Id); var schema = content.Value.Schema; Assert.Collection(schema.Properties, property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("nested", property.Key); - Assert.Equal("NestedType", property.Value.Reference.Id); + Assert.Equal("NestedType", ((OpenApiSchemaReference)property.Value).Reference.Id); }); }); } @@ -510,18 +506,18 @@ await VerifyOpenApiDocument(builder, document => var operation = document.Paths["/api"].Operations[OperationType.Post]; var requestBody = operation.RequestBody; var content = Assert.Single(requestBody.Content); - Assert.Equal("NestedType", content.Value.Schema.Reference.Id); + Assert.Equal("NestedType", ((OpenApiSchemaReference)content.Value.Schema).Reference.Id); var schema = content.Value.Schema; Assert.Collection(schema.Properties, property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("nested", property.Key); - Assert.Equal("NestedType", property.Value.Reference.Id); + Assert.Equal("NestedType", ((OpenApiSchemaReference)property.Value).Reference.Id); }); }); } @@ -578,19 +574,16 @@ await VerifyOpenApiDocument(builder, document => { var property = schema.Properties["name"]; Assert.Equal(JsonSchemaType.String, property.Type); - Assert.False(property.Nullable); }, schema => { var property = schema.Properties["number"]; Assert.Equal(JsonSchemaType.Integer, property.Type); - Assert.False(property.Nullable); }, schema => { var property = schema.Properties["ids"]; Assert.Equal(JsonSchemaType.Array, property.Type); - Assert.False(property.Nullable); }); }); } @@ -674,19 +667,19 @@ await VerifyOpenApiDocument(builder, document => var operation = document.Paths["/api"].Operations[OperationType.Post]; var requestBody = operation.RequestBody; var content = Assert.Single(requestBody.Content); - var schema = document.Components.Schemas[content.Value.Schema.Reference.Id]; + var schema = content.Value.Schema; Assert.Collection(schema.Properties, property => { Assert.Equal("selfReferenceList", property.Key); Assert.Equal(JsonSchemaType.Null | JsonSchemaType.Array, property.Value.Type); - Assert.Equal("Parent", property.Value.Items.Reference.Id); + Assert.Equal("Parent", ((OpenApiSchemaReference)property.Value.Items).Reference.Id); }, property => { Assert.Equal("selfReferenceDictionary", property.Key); Assert.Equal(JsonSchemaType.Null | JsonSchemaType.Object, property.Value.Type); - Assert.Equal("Parent", property.Value.AdditionalProperties.Reference.Id); + Assert.Equal("Parent", ((OpenApiSchemaReference)property.Value.AdditionalProperties).Reference.Id); }); }); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs index 7e3ca958936e..071b4f679c5c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs @@ -80,7 +80,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -128,7 +128,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); Assert.Equal(5, property.Value.MinLength); }, property => @@ -172,7 +172,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -247,7 +247,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -257,8 +257,6 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("createdAt", property.Key); - // DateTime schema appears twice in the document so we expect - // this to map to a reference ID. var dateTimeSchema = property.Value; Assert.Equal(JsonSchemaType.String, dateTimeSchema.Type); Assert.Equal("date-time", dateTimeSchema.Format); @@ -304,7 +302,7 @@ await VerifyOpenApiDocument(builder, document => }, property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("completed", property.Key); @@ -328,7 +326,7 @@ await VerifyOpenApiDocument(builder, document => }, property => { Assert.Equal("message", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); }); }); @@ -369,7 +367,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("make", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }); }); } @@ -402,7 +400,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("name", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -419,7 +417,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -467,7 +465,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -545,7 +543,7 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -587,28 +585,28 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("type", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("status", property.Key); - Assert.Equal(JsonSchemaType.Integer, property.Value.Type); + Assert.Equal(JsonSchemaType.Integer | JsonSchemaType.Null, property.Value.Type); Assert.Equal("int32", property.Value.Format); }, property => { Assert.Equal("detail", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { Assert.Equal("instance", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { @@ -646,7 +644,6 @@ await VerifyOpenApiDocument(builder, document => { Assert.Equal("object", property.Key); Assert.Null(property.Value.Type); - Assert.False(property.Value.Nullable); }, property => { @@ -681,7 +678,7 @@ await VerifyOpenApiDocument(actionDescriptor, document => property => { Assert.Equal("title", property.Key); - Assert.Equal(JsonSchemaType.String, property.Value.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type); }, property => { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs index 6e76e7e290f2..6fb07c0035bb 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; public class OpenApiSchemaReferenceTransformerTests : OpenApiDocumentServiceTestBase @@ -54,14 +55,17 @@ await VerifyOpenApiDocument(builder, document => // } // } // } - Assert.Equal(schema.Reference, schema2.Reference); + Assert.Equal(JsonSchemaType.Object, schema.Type); + var value = Assert.Single(schema.Properties).Value; + Assert.Equal("IFormFile", ((OpenApiSchemaReference)value).Reference.Id); - var effectiveSchema = schema; - Assert.Equal(JsonSchemaType.Object, effectiveSchema.Type); - Assert.Single(effectiveSchema.Properties); - var effectivePropertySchema = effectiveSchema.Properties["value"]; - Assert.Equal(JsonSchemaType.String, effectivePropertySchema.Type); - Assert.Equal("binary", effectivePropertySchema.Format); + Assert.Equal(JsonSchemaType.Object, schema2.Type); + var value2 = Assert.Single(schema2.Properties).Value; + Assert.Equal("IFormFile", ((OpenApiSchemaReference)value2).Reference.Id); + + var effectiveSchema = ((OpenApiSchemaReference)value).Target; + Assert.Equal(JsonSchemaType.String, effectiveSchema.Type); + Assert.Equal("binary", effectiveSchema.Format); }); } @@ -102,7 +106,8 @@ await VerifyOpenApiDocument(builder, document => // } // } // } - Assert.Equal(requestBodySchema.Reference.Id, responseSchema.Reference.Id); + // } + Assert.Equal(((OpenApiSchemaReference)requestBodySchema).Reference.Id, ((OpenApiSchemaReference)responseSchema).Reference.Id); var effectiveSchema = requestBodySchema; Assert.Equal(JsonSchemaType.Object, effectiveSchema.Type); @@ -110,7 +115,7 @@ await VerifyOpenApiDocument(builder, document => var effectiveIdSchema = effectiveSchema.Properties["id"]; Assert.Equal(JsonSchemaType.Integer, effectiveIdSchema.Type); var effectiveTitleSchema = effectiveSchema.Properties["title"]; - Assert.Equal(JsonSchemaType.String, effectiveTitleSchema.Type); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, effectiveTitleSchema.Type); var effectiveCompletedSchema = effectiveSchema.Properties["completed"]; Assert.Equal(JsonSchemaType.Boolean, effectiveCompletedSchema.Type); var effectiveCreatedAtSchema = effectiveSchema.Properties["createdAt"]; @@ -171,7 +176,7 @@ await VerifyOpenApiDocument(builder, document => Assert.Equal(JsonSchemaType.Array, requestBodySchema.Type); Assert.Equal(JsonSchemaType.Object, requestBodySchema2.Type); // Values of the list and dictionary point to the same reference ID - Assert.Equal(requestBodySchema.Items.Reference.Id, requestBodySchema2.AdditionalProperties.Reference.Id); + Assert.Equal(((OpenApiSchemaReference)requestBodySchema.Items).Reference.Id, ((OpenApiSchemaReference)requestBodySchema2.AdditionalProperties).Reference.Id); }); } @@ -197,7 +202,7 @@ await VerifyOpenApiDocument(builder, document => var requestBodySchema2 = requestBody2.Schema; // Todo parameter (second parameter) in allOf for each operation should point to the same reference ID. - Assert.Equal(requestBodySchema.AllOf[1].Reference.Id, requestBodySchema2.AllOf[1].Reference.Id); + Assert.Equal(((OpenApiSchemaReference)requestBodySchema.AllOf[1]).Reference.Id, ((OpenApiSchemaReference)requestBodySchema2.AllOf[1]).Reference.Id); // IFormFile parameter should use inline schema since it only appears once in the application. Assert.Equal(JsonSchemaType.Object, requestBodySchema.AllOf[0].Type); @@ -206,8 +211,8 @@ await VerifyOpenApiDocument(builder, document => // string parameter is not resolved to a top-level reference. Assert.Equal(JsonSchemaType.Object, requestBodySchema2.AllOf[0].Type); - Assert.Null(requestBodySchema.AllOf[1].Properties["title"].Reference); - Assert.Null(requestBodySchema2.AllOf[1].Properties["title"].Reference); + Assert.IsNotType(requestBodySchema.AllOf[1].Properties["title"]); + Assert.IsNotType(requestBodySchema2.AllOf[1].Properties["title"]); }); } @@ -256,13 +261,13 @@ await VerifyOpenApiDocument(builder, document => // } // Both list types should be inlined - Assert.Null(requestBodySchema.Reference); - Assert.Equal(requestBodySchema.Reference, requestBodySchema2.Reference); + Assert.IsNotType(requestBodySchema); + Assert.IsNotType(requestBodySchema2); // And have an `array` type Assert.Equal(JsonSchemaType.Array, requestBodySchema.Type); // With an `items` sub-schema should consist of a $ref to Todo - Assert.Equal("Todo", requestBodySchema.Items.Reference.Id); - Assert.Equal(requestBodySchema.Items.Reference.Id, requestBodySchema2.Items.Reference.Id); + Assert.Equal("Todo", ((OpenApiSchemaReference)requestBodySchema.Items).Reference.Id); + Assert.Equal(((OpenApiSchemaReference)requestBodySchema.Items).Reference.Id, ((OpenApiSchemaReference)requestBodySchema2.Items).Reference.Id); Assert.Equal(4, requestBodySchema.Items.Properties.Count); }); } @@ -293,7 +298,7 @@ await VerifyOpenApiDocument(builder, options, document => var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; // Schemas are distinct because of applied transformer so no reference is used. - Assert.NotEqual(requestSchema.Reference.Id, responseSchema.Reference.Id); + Assert.NotEqual(((OpenApiSchemaReference)requestSchema).Reference.Id, ((OpenApiSchemaReference)responseSchema).Reference.Id); Assert.Equal("todo", ((OpenApiAny)requestSchema.Extensions["x-my-extension"]).Node.GetValue()); Assert.False(responseSchema.Extensions.TryGetValue("x-my-extension", out var _)); }); @@ -346,14 +351,14 @@ static void VerifyDocument(OpenApiDocument document) // } // Both container types should point to the same reference ID - Assert.Equal("TodoListContainer", requestBodySchema.Reference.Id); - Assert.Equal(requestBodySchema.Reference.Id, requestBodySchema2.Reference.Id); + Assert.Equal("TodoListContainer", ((OpenApiSchemaReference)requestBodySchema).Reference.Id); + Assert.Equal(((OpenApiSchemaReference)requestBodySchema).Reference.Id, ((OpenApiSchemaReference)requestBodySchema2).Reference.Id); // The referenced schema should have an array type with items pointing to Todo var effectiveSchema = requestBodySchema; var todosProperty = effectiveSchema.Properties["todos"]; Assert.Equal(JsonSchemaType.Null | JsonSchemaType.Array, todosProperty.Type); var itemsSchema = todosProperty.Items; - Assert.Equal("Todo", itemsSchema.Reference.Id); + Assert.Equal("Todo", ((OpenApiSchemaReference)itemsSchema).Reference.Id); Assert.Equal(4, itemsSchema.Properties.Count); } } @@ -377,19 +382,19 @@ await VerifyOpenApiDocument(builder, document => var requestSchema = operation.RequestBody.Content["application/json"].Schema; // Assert $ref used for top-level - Assert.Equal("Level1", requestSchema.Reference.Id); + Assert.Equal("Level1", ((OpenApiSchemaReference)requestSchema).Reference.Id); // Assert that $ref is used for Level1.Item2 var level1Schema = requestSchema; - Assert.Equal("Level2", level1Schema.Properties["item2"].Reference.Id); + Assert.Equal("Level2", ((OpenApiSchemaReference)level1Schema.Properties["item2"]).Reference.Id); // Assert that $ref is used for Level2.Item3 var level2Schema = level1Schema.Properties["item2"]; - Assert.Equal("Level3", level2Schema.Properties["item3"].Reference.Id); + Assert.Equal("Level3", ((OpenApiSchemaReference)level2Schema.Properties["item3"]).Reference.Id); // Assert that no $ref is used for string property var level3Schema = level2Schema.Properties["item3"]; - Assert.Null(level3Schema.Properties["terminate"].Reference); + Assert.IsNotType(level3Schema.Properties["terminate"]); }); } @@ -441,45 +446,18 @@ await VerifyOpenApiDocument(builder, document => var requestSchema = operation.RequestBody.Content["application/json"].Schema; // Assert $ref used for top-level - Assert.Equal("DeeplyNestedLevel1", requestSchema.Reference.Id); + Assert.Equal("DeeplyNestedLevel1", ((OpenApiSchemaReference)requestSchema).Reference.Id); // Assert that $ref is used for all nested levels var levelSchema = requestSchema; for (var level = 2; level < 36; level++) { - Assert.Equal($"DeeplyNestedLevel{level}", levelSchema.Properties[$"item{level}"].Reference.Id); + Assert.Equal($"DeeplyNestedLevel{level}", ((OpenApiSchemaReference)levelSchema.Properties[$"item{level}"]).Reference.Id); levelSchema = levelSchema.Properties[$"item{level}"]; } }); } - [Fact] - public async Task SelfReferenceMapperOnlyOperatesOnSchemaReferenceTypes() - { - var builder = CreateBuilder(); - - builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now)); - - var options = new OpenApiOptions(); - options.AddSchemaTransformer((schema, context, cancellationToken) => - { - if (context.JsonTypeInfo.Type == typeof(Todo)) - { - schema.Reference = new OpenApiReference { Id = "#", Type = ReferenceType.Link }; - } - return Task.CompletedTask; - }); - - await VerifyOpenApiDocument(builder, options, document => - { - var operation = document.Paths["/todo"].Operations[OperationType.Get]; - var response = operation.Responses["200"].Content["application/json"]; - var responseSchema = response.Schema; - Assert.Equal("#", responseSchema.Reference.Id); - Assert.Equal(ReferenceType.Link, responseSchema.Reference.Type); - }); - } - [Fact] public async Task SupportsNestedSchemasWithSelfReference() { @@ -494,7 +472,7 @@ await VerifyOpenApiDocument(builder, document => var requestSchema = operation.RequestBody.Content["application/json"].Schema; // Assert $ref used for top-level - Assert.Equal("LocationContainer", requestSchema.Reference.Id); + Assert.Equal("LocationContainer", ((OpenApiSchemaReference)requestSchema).Reference.Id); // Assert that only expected schema references are generated Assert.Equal(3, document.Components.Schemas.Count); @@ -571,7 +549,7 @@ await VerifyOpenApiDocument(builder, document => var requestSchema = operation.RequestBody.Content["application/json"].Schema; // Assert $ref used for top-level - Assert.Equal("ParentObject", requestSchema.Reference.Id); + Assert.Equal("ParentObject", ((OpenApiSchemaReference)requestSchema).Reference.Id); // Assert that only two schemas are generated Assert.Equal(2, document.Components.Schemas.Count); @@ -640,13 +618,13 @@ await VerifyOpenApiDocument(builder, document => var requestSchema = operation.RequestBody.Content["application/json"].Schema; // Assert $ref used for top-level - Assert.Equal("Root", requestSchema.Reference.Id); + Assert.Equal("Root", ((OpenApiSchemaReference)requestSchema).Reference.Id); // Assert that $ref is used for nested Item1 - Assert.Equal("Item", requestSchema.Properties["item1"].Reference.Id); + Assert.Equal("Item", ((OpenApiSchemaReference)requestSchema.Properties["item1"]).Reference.Id); // Assert that $ref is used for nested Item2 - Assert.Equal("Item", requestSchema.Properties["item2"].Reference.Id); + Assert.Equal("Item", ((OpenApiSchemaReference)requestSchema.Properties["item2"]).Reference.Id); }); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs index 6c9857ac1709..699512213905 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; public class SchemaTransformerTests : OpenApiDocumentServiceTestBase { @@ -505,7 +506,7 @@ await VerifyOpenApiDocument(builder, options, document => var path = document.Paths["/shape"]; var postOperation = path.Operations[OperationType.Post]; var requestSchema = postOperation.RequestBody.Content["application/json"].Schema; - var triangleSubschema = Assert.Single(requestSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); + var triangleSubschema = Assert.Single(requestSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeTriangle")); Assert.True(triangleSubschema.Extensions.TryGetValue("x-my-extension", out var _)); // Assert that the standalone `Triangle` type has been updated @@ -580,13 +581,13 @@ await VerifyOpenApiDocument(builder, options, document => var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; var itemSchema = responseSchema.Items; - var triangleSubschema = Assert.Single(itemSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); + var triangleSubschema = Assert.Single(itemSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeTriangle")); // Assert that the x-my-extension type is set to this-is-a-triangle Assert.True(triangleSubschema.Extensions.TryGetValue("x-my-extension", out var triangleExtension)); Assert.Equal("this-is-a-triangle", ((OpenApiAny)triangleExtension).Node.GetValue()); // Assert that the `Square` type within the polymorphic type list has been updated - var squareSubschema = Assert.Single(itemSchema.AnyOf.Where(s => s.Reference.Id == "ShapeSquare")); + var squareSubschema = Assert.Single(itemSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeSquare")); // Assert that the x-my-extension type is set to this-is-a-square Assert.True(squareSubschema.Extensions.TryGetValue("x-my-extension", out var squareExtension)); Assert.Equal("this-is-a-square", ((OpenApiAny)squareExtension).Node.GetValue()); @@ -621,13 +622,13 @@ await VerifyOpenApiDocument(builder, options, document => var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; var someShapeSchema = responseSchema.Properties["someShape"]; - var triangleSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); + var triangleSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeTriangle")); // Assert that the x-my-extension type is set to this-is-a-triangle Assert.True(triangleSubschema.Extensions.TryGetValue("x-my-extension", out var triangleExtension)); Assert.Equal("this-is-a-triangle", ((OpenApiAny)triangleExtension).Node.GetValue()); // Assert that the `Square` type within the polymorphic type list has been updated - var squareSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => s.Reference.Id == "ShapeSquare")); + var squareSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeSquare")); // Assert that the x-my-extension type is set to this-is-a-square Assert.True(squareSubschema.Extensions.TryGetValue("x-my-extension", out var squareExtension)); Assert.Equal("this-is-a-square", ((OpenApiAny)squareExtension).Node.GetValue()); @@ -662,13 +663,13 @@ await VerifyOpenApiDocument(builder, options, document => var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; var someShapeSchema = responseSchema.Items.Properties["someShape"]; - var triangleSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); + var triangleSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeTriangle")); // Assert that the x-my-extension type is set to this-is-a-triangle Assert.True(triangleSubschema.Extensions.TryGetValue("x-my-extension", out var triangleExtension)); Assert.Equal("this-is-a-triangle", ((OpenApiAny)triangleExtension).Node.GetValue()); // Assert that the `Square` type within the polymorphic type list has been updated - var squareSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => s.Reference.Id == "ShapeSquare")); + var squareSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeSquare")); // Assert that the x-my-extension type is set to this-is-a-square Assert.True(squareSubschema.Extensions.TryGetValue("x-my-extension", out var squareExtension)); Assert.Equal("this-is-a-square", ((OpenApiAny)squareExtension).Node.GetValue()); @@ -747,7 +748,7 @@ await VerifyOpenApiDocument(builder, options, document => var shapePath = document.Paths["/shape"]; var shapeOperation = shapePath.Operations[OperationType.Post]; var shapeRequestSchema = shapeOperation.RequestBody.Content["application/json"].Schema; - var triangleSchema = Assert.Single(shapeRequestSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); + var triangleSchema = Assert.Single(shapeRequestSchema.AnyOf.Where(s => ((OpenApiSchemaReference)s).Reference.Id == "ShapeTriangle")); Assert.True(((OpenApiAny)triangleSchema.Not.Extensions["modified-by-not-schema-transformer"]).Node.GetValue()); }); @@ -757,7 +758,8 @@ static void UseNotSchemaTransformer(OpenApiOptions options, Func