diff --git a/eng/Versions.props b/eng/Versions.props index e8ac2069fc78..855ecbfcf973 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -329,8 +329,8 @@ $(XunitVersion) 2.4.3 4.0.5 - 1.6.13 - 1.6.13 + 1.6.17 + 1.6.17 6.0.322601 1.10.93 diff --git a/eng/testing/linker/project.csproj.template b/eng/testing/linker/project.csproj.template index 68e6ab7b07b4..ea368a6caa54 100644 --- a/eng/testing/linker/project.csproj.template +++ b/eng/testing/linker/project.csproj.template @@ -16,8 +16,6 @@ $(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.Http.Generated false - - $(NoWarn);IL2104 {AdditionalProperties} @@ -29,19 +27,4 @@ {AdditionalProjectReferences} - - - - - - - - true - true - - - - diff --git a/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs b/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs index d6c7d3362110..7990446ab26e 100644 --- a/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs @@ -42,7 +42,6 @@ public bool Equals(IOpenApiAny? x, IOpenApiAny? y) OpenApiByte byteX => y is OpenApiByte byteY && byteX.Value.SequenceEqual(byteY.Value), OpenApiDate dateX => y is OpenApiDate dateY && dateX.Value == dateY.Value, OpenApiDateTime dateTimeX => y is OpenApiDateTime dateTimeY && dateTimeX.Value == dateTimeY.Value, - ScrubbedOpenApiAny scrubbedX => y is ScrubbedOpenApiAny scrubbedY && scrubbedX.Value == scrubbedY.Value, _ => x.Equals(y) }); } @@ -74,7 +73,6 @@ public int GetHashCode(IOpenApiAny obj) OpenApiPassword password => password.Value, OpenApiDate date => date.Value, OpenApiDateTime dateTime => dateTime.Value, - ScrubbedOpenApiAny scrubbed => scrubbed.Value, _ => null }); diff --git a/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs b/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs index daf42772d89c..ef446367817a 100644 --- a/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs @@ -65,7 +65,34 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y) x.UniqueItems == y.UniqueItems && x.UnresolvedReference == y.UnresolvedReference && x.WriteOnly == y.WriteOnly && - OpenApiXmlComparer.Instance.Equals(x.Xml, y.Xml); + OpenApiXmlComparer.Instance.Equals(x.Xml, y.Xml) && + SchemaIdEquals(x, y); + } + + private static bool SchemaIdEquals(OpenApiSchema x, OpenApiSchema y) + { + if (x.Annotations == null && y.Annotations == null) + { + return true; + } + if (x.Annotations == null || y.Annotations == null) + { + return false; + } + if (x.Annotations.TryGetValue(OpenApiConstants.SchemaId, out var xSchemaId) + && y.Annotations.TryGetValue(OpenApiConstants.SchemaId, out var ySchemaId)) + { + if (xSchemaId == null && ySchemaId == null) + { + return true; + } + if (xSchemaId == null || ySchemaId == null) + { + return false; + } + return xSchemaId.Equals(ySchemaId); + } + return true; } public int GetHashCode(OpenApiSchema obj) diff --git a/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs b/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs index a5b7626ef6b5..ee105edddf6b 100644 --- a/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs +++ b/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Writers; namespace Microsoft.AspNetCore.Builder; @@ -48,7 +49,7 @@ public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder e using var writer = Utf8BufferTextWriter.Get(output); try { - document.Serialize(new ScrubbingOpenApiJsonWriter(writer), documentOptions.OpenApiVersion); + document.Serialize(new OpenApiJsonWriter(writer), documentOptions.OpenApiVersion); context.Response.ContentType = "application/json;charset=utf-8"; await context.Response.BodyWriter.WriteAsync(output.ToArray(), context.RequestAborted); await context.Response.BodyWriter.FlushAsync(context.RequestAborted); diff --git a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs index bf683f5afac1..6ad6b03af319 100644 --- a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs +++ b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs @@ -304,7 +304,8 @@ public static void ReadProperty(ref Utf8JsonReader reader, string propertyName, break; case OpenApiConstants.SchemaId: reader.Read(); - schema.Extensions.Add(OpenApiConstants.SchemaId, new ScrubbedOpenApiAny(reader.GetString())); + schema.Annotations ??= new Dictionary(); + schema.Annotations.Add(OpenApiConstants.SchemaId, reader.GetString()); break; // OpenAPI does not support the `const` keyword in its schema implementation, so // we map it to its closest approximation, an enum with a single value, here. diff --git a/src/OpenApi/src/Services/OpenApiDocumentProvider.cs b/src/OpenApi/src/Services/OpenApiDocumentProvider.cs index dd1b53e90ea6..6c453e177ebd 100644 --- a/src/OpenApi/src/Services/OpenApiDocumentProvider.cs +++ b/src/OpenApi/src/Services/OpenApiDocumentProvider.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Microsoft.OpenApi; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Writers; using System.Linq; namespace Microsoft.Extensions.ApiDescriptions; @@ -44,7 +45,7 @@ public async Task GenerateAsync(string documentName, TextWriter writer, OpenApiS // more info. var targetDocumentService = serviceProvider.GetRequiredKeyedService(documentName); var document = await targetDocumentService.GetOpenApiDocumentAsync(); - var jsonWriter = new ScrubbingOpenApiJsonWriter(writer); + var jsonWriter = new OpenApiJsonWriter(writer); document.Serialize(jsonWriter, openApiSpecVersion); } diff --git a/src/OpenApi/src/Services/OpenApiDocumentService.cs b/src/OpenApi/src/Services/OpenApiDocumentService.cs index fed6794bf191..a351b75af827 100644 --- a/src/OpenApi/src/Services/OpenApiDocumentService.cs +++ b/src/OpenApi/src/Services/OpenApiDocumentService.cs @@ -99,9 +99,9 @@ internal async Task ForEachOperationAsync( continue; } - if (operation.Extensions.TryGetValue(OpenApiConstants.DescriptionId, out var descriptionIdExtension) && - descriptionIdExtension is ScrubbedOpenApiAny { Value: string descriptionId } && - TryGetCachedOperationTransformerContext(descriptionId, out var operationContext)) + if (operation.Annotations.TryGetValue(OpenApiConstants.DescriptionId, out var descriptionId) && + descriptionId is string descriptionIdString && + TryGetCachedOperationTransformerContext(descriptionIdString, out var operationContext)) { await callback(operation, operationContext, cancellationToken); } @@ -169,7 +169,8 @@ private async Task> GetOperationsAsy foreach (var description in descriptions) { var operation = await GetOperationAsync(description, capturedTags, cancellationToken); - operation.Extensions.Add(OpenApiConstants.DescriptionId, new ScrubbedOpenApiAny(description.ActionDescriptor.Id)); + operation.Annotations ??= new Dictionary(); + operation.Annotations.Add(OpenApiConstants.DescriptionId, description.ActionDescriptor.Id); var operationContext = new OpenApiOperationTransformerContext { diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs index c206176f8194..12698a2d9e45 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs @@ -104,9 +104,7 @@ public void PopulateSchemaIntoReferenceCache(OpenApiSchema schema, bool captureS // AnyOf schemas in a polymorphic type should contain a reference to the parent schema // ID to support disambiguating between a derived type on its own and a derived type // as part of a polymorphic schema. - var baseTypeSchemaId = schema.Extensions.TryGetValue(OpenApiConstants.SchemaId, out var schemaId) - ? ((ScrubbedOpenApiAny)schemaId).Value - : null; + var baseTypeSchemaId = schema.Annotations is not null && schema.Annotations.TryGetValue(OpenApiConstants.SchemaId, out var schemaId) ? schemaId?.ToString() : null; foreach (var anyOfSchema in schema.AnyOf) { AddOrUpdateSchemaByReference(anyOfSchema, baseTypeSchemaId); @@ -176,8 +174,8 @@ private void AddOrUpdateSchemaByReference(OpenApiSchema schema, string? baseType private static string? GetSchemaReferenceId(OpenApiSchema schema) { - if (schema.Extensions.TryGetValue(OpenApiConstants.SchemaId, out var referenceIdAny) - && referenceIdAny is ScrubbedOpenApiAny { Value: string referenceId }) + if (schema.Annotations?.TryGetValue(OpenApiConstants.SchemaId, out var referenceIdObject) == true + && referenceIdObject is string referenceId) { return referenceId; } diff --git a/src/OpenApi/src/Writers/ScrubbedOpenApiAny.cs b/src/OpenApi/src/Writers/ScrubbedOpenApiAny.cs deleted file mode 100644 index 939ca06496c5..000000000000 --- a/src/OpenApi/src/Writers/ScrubbedOpenApiAny.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Writers; - -namespace Microsoft.AspNetCore.OpenApi; - -/// -/// Represents an instance that does not serialize itself to -/// the outgoing document. -/// -/// The no-op implementation of the method -/// prevents the value of these properties from being written to disk. When used in conjunction with -/// the logic to exempt these properties from serialization in , -/// we achieve the desired result of not serializing these properties to the output document but retaining -/// them in the in-memory document. -/// -internal sealed class ScrubbedOpenApiAny(string? value) : IOpenApiAny -{ - public AnyType AnyType { get; } = AnyType.Primitive; - - public string? Value { get; } = value; - - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - return; - } -} diff --git a/src/OpenApi/src/Writers/ScrubbingOpenApiJsonWriter.cs b/src/OpenApi/src/Writers/ScrubbingOpenApiJsonWriter.cs deleted file mode 100644 index fdf9a00210d9..000000000000 --- a/src/OpenApi/src/Writers/ScrubbingOpenApiJsonWriter.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.OpenApi.Writers; - -namespace Microsoft.AspNetCore.OpenApi; - -/// -/// Represents a JSON writer that scrubs certain properties from the output, -/// specifically the schema ID and description ID that are used for schema resolution -/// and action descriptor resolution in the in-memory OpenAPI document. -/// -/// In conjunction with this allows us to work around -/// the lack of an in-memory property bag on the OpenAPI object model and allows us to -/// avoid having to scrub the properties in the OpenAPI document prior to serialization. -/// -/// For more information, see https://github.com/microsoft/OpenAPI.NET/issues/1719. -/// -internal sealed class ScrubbingOpenApiJsonWriter(TextWriter textWriter) : OpenApiJsonWriter(textWriter) -{ - public override void WritePropertyName(string name) - { - if (name == OpenApiConstants.SchemaId || name == OpenApiConstants.DescriptionId) - { - return; - } - - base.WritePropertyName(name); - } -} diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiAnyComparerTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiAnyComparerTests.cs index 7ef5afdfe2bb..18ab3e1b27b3 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiAnyComparerTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiAnyComparerTests.cs @@ -40,9 +40,6 @@ public class OpenApiAnyComparerTests [new OpenApiArray { new OpenApiString("value") }, new OpenApiArray { new OpenApiString("value2") }, false], [new OpenApiArray { new OpenApiString("value2"), new OpenApiString("value") }, new OpenApiArray { new OpenApiString("value"), new OpenApiString("value2") }, false], [new OpenApiArray { new OpenApiString("value"), new OpenApiString("value") }, new OpenApiArray { new OpenApiString("value"), new OpenApiString("value") }, true], - [new ScrubbedOpenApiAny("value"), new ScrubbedOpenApiAny("value"), true], - [new ScrubbedOpenApiAny("value"), new ScrubbedOpenApiAny("value2"), false], - [new ScrubbedOpenApiAny(null), new ScrubbedOpenApiAny(null), true] ]; [Theory] diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiSchemaComparerTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiSchemaComparerTests.cs index 3678c4c58734..56fb593c526b 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiSchemaComparerTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Comparers/OpenApiSchemaComparerTests.cs @@ -144,6 +144,7 @@ public void ValidatePropertiesOnOpenApiSchema() UnresolvedReference = true, WriteOnly = true, Xml = new OpenApiXml { Name = "Name" }, + Annotations = new Dictionary { ["key"] = "value" } }; OpenApiSchema modifiedSchema = new(originalSchema) { AdditionalProperties = new OpenApiSchema { Type = "string" } }; @@ -302,6 +303,11 @@ public void ValidatePropertiesOnOpenApiSchema() Assert.False(OpenApiSchemaComparer.Instance.Equals(originalSchema, modifiedSchema)); Assert.True(propertyNames.Remove(nameof(OpenApiSchema.Xml))); + modifiedSchema = new(originalSchema); + modifiedSchema.Annotations["key"] = "another value"; + Assert.False(OpenApiSchemaComparer.Instance.Equals(originalSchema, modifiedSchema)); + Assert.True(propertyNames.Remove(nameof(OpenApiSchema.Annotations))); + Assert.Empty(propertyNames); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/OpenApiDocumentIntegrationTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/OpenApiDocumentIntegrationTests.cs index 9655c4d09c53..def8474834f9 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/OpenApiDocumentIntegrationTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/OpenApiDocumentIntegrationTests.cs @@ -32,7 +32,7 @@ await Verifier.Verify(GetOpenApiJson(document)) private static string GetOpenApiJson(OpenApiDocument document) { using var textWriter = new StringWriter(CultureInfo.InvariantCulture); - var jsonWriter = new ScrubbingOpenApiJsonWriter(textWriter); + var jsonWriter = new OpenApiJsonWriter(textWriter); document.SerializeAsV3(jsonWriter); return textWriter.ToString(); }