From d8eda7aca828415f253aa6f0ac3780f303719f51 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 3 Jul 2024 18:05:02 -0700 Subject: [PATCH 01/19] Make library trim-compatible --- .../Any/OpenApiAnyCloneHelper.cs | 11 +- .../Attributes/TrimmingAttributes.cs | 145 ++++++++++++++++++ .../Extensions/EnumExtensions.cs | 103 +++++++++++++ .../Models/OpenApiReference.cs | 4 +- 4 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs index b0e553f71..c2f76b08c 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Reflection; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.OpenApi.Any { @@ -15,17 +15,16 @@ public class OpenApiAnyCloneHelper /// /// The object instance. /// A clone copy or the object itself. - public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) + public static IOpenApiAny CloneFromCopyConstructor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(T obj) where T : IOpenApiAny { if (obj != null) { - var t = obj.GetType(); - foreach (var ci in t.GetConstructors()) + foreach (var ci in typeof(T).GetConstructors()) { var pi = ci.GetParameters(); - if (pi.Length == 1 && pi[0].ParameterType == t) + if (pi.Length == 1 && pi[0].ParameterType == typeof(T)) { - return (IOpenApiAny)ci.Invoke(new object[] { obj }); + return (IOpenApiAny)ci.Invoke([obj]); } } } diff --git a/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs b/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs new file mode 100644 index 000000000..8c84377fd --- /dev/null +++ b/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +#nullable enable + +namespace System.Diagnostics.CodeAnalysis +{ +#if !NET7_0_OR_GREATER + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// When this attribute is applied to a class, interface, or struct, the members specified + /// can be accessed dynamically on instances returned from calling + /// on instances of that class, interface, or struct. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None + } +#endif +} diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index 4e2e795d3..bc77d2133 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using Microsoft.OpenApi.Attributes; +using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Extensions { @@ -42,5 +43,107 @@ public static string GetDisplayName(this Enum enumValue) var attribute = enumValue.GetAttributeOfType(); return attribute == null ? enumValue.ToString() : attribute.Name; } + + /// + /// Gets the enum display name for without the use of reflection. + /// + /// The type of the enum value. + /// The enum value. + /// The display string to use. + public static string GetDisplayName(this T enumValue) where T : Enum + { + return enumValue switch + { + ParameterStyle parameterStyle => parameterStyle.GetDisplayName(), + ParameterLocation parameterLocation => parameterLocation.GetDisplayName(), + ReferenceType referenceType => referenceType.GetDisplayName(), + OperationType operationType => operationType.GetDisplayName(), + SecuritySchemeType securitySchemeType => securitySchemeType.GetDisplayName(), + _ => enumValue.ToString() + }; + } + + /// + /// Gets the enum display for name without the use of reflection. + /// + /// The enum value. + /// The display string to use. + internal static string GetDisplayName(this ParameterStyle parameterStyle) => parameterStyle switch + { + ParameterStyle.Matrix => "matrix", + ParameterStyle.Label => "label", + ParameterStyle.Form => "form", + ParameterStyle.Simple => "simple", + ParameterStyle.SpaceDelimited => "spaceDelimited", + ParameterStyle.PipeDelimited => "pipeDelimited", + ParameterStyle.DeepObject => "deepObject", + _ => parameterStyle.ToString() + }; + + /// + /// Gets the enum display for name without the use of reflection. + /// + /// The enum value. + /// The display string to use. + internal static string GetDisplayName(this ParameterLocation parameterLocation) => parameterLocation switch + { + ParameterLocation.Query => "query", + ParameterLocation.Header => "header", + ParameterLocation.Path => "path", + ParameterLocation.Cookie => "cookie", + _ => parameterLocation.ToString() + }; + + /// + /// Gets the enum display for name without the use of reflection. + /// + /// The enum value. + /// The display string to use. + internal static string GetDisplayName(this ReferenceType referenceType) => referenceType switch + { + ReferenceType.Schema => "schemas", + ReferenceType.Response => "responses", + ReferenceType.Parameter => "parameters", + ReferenceType.Example => "examples", + ReferenceType.RequestBody => "requestBodies", + ReferenceType.Header => "headers", + ReferenceType.SecurityScheme => "securitySchemes", + ReferenceType.Link => "links", + ReferenceType.Callback => "callbacks", + ReferenceType.Tag => "tags", + _ => referenceType.ToString() + }; + + /// + /// Gets the enum display for name without the use of reflection. + /// + /// The enum value. + /// The display string to use. + internal static string GetDisplayName(this OperationType operationType) => operationType switch + { + OperationType.Get => "get", + OperationType.Put => "put", + OperationType.Post => "post", + OperationType.Delete => "delete", + OperationType.Options => "options", + OperationType.Head => "head", + OperationType.Patch => "patch", + OperationType.Trace => "trace", + _ => operationType.ToString() + }; + + /// + /// Gets the enum display for name without the use of reflection. + /// + /// The enum value. + /// The display string to use. + internal static string GetDisplayName(this SecuritySchemeType securitySchemeType) => securitySchemeType switch + { + SecuritySchemeType.ApiKey => "apiKey", + SecuritySchemeType.Http => "http", + SecuritySchemeType.OAuth2 => "oauth2", + SecuritySchemeType.OpenIdConnect => "openIdConnect", + _ => securitySchemeType.ToString() + }; } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index e366bf10d..339b9c632 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -83,7 +83,7 @@ public string ReferenceV3 return Id; } - return "#/components/" + Type.GetDisplayName() + "/" + Id; + return "#/components/" + Type.Value.GetDisplayName() + "/" + Id; } } @@ -201,7 +201,7 @@ private string GetExternalReferenceV3() return ExternalResource + "#" + Id; } - return ExternalResource + "#/components/" + Type.GetDisplayName() + "/"+ Id; + return ExternalResource + "#/components/" + Type.Value.GetDisplayName() + "/"+ Id; } return ExternalResource; From 08718e15e72f4236e37f160e57f1767c8428b8f9 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 3 Jul 2024 21:57:46 -0700 Subject: [PATCH 02/19] Avoid using generic overload for all enum types --- .../Extensions/EnumExtensions.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index bc77d2133..81fc65a1b 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -44,25 +44,6 @@ public static string GetDisplayName(this Enum enumValue) return attribute == null ? enumValue.ToString() : attribute.Name; } - /// - /// Gets the enum display name for without the use of reflection. - /// - /// The type of the enum value. - /// The enum value. - /// The display string to use. - public static string GetDisplayName(this T enumValue) where T : Enum - { - return enumValue switch - { - ParameterStyle parameterStyle => parameterStyle.GetDisplayName(), - ParameterLocation parameterLocation => parameterLocation.GetDisplayName(), - ReferenceType referenceType => referenceType.GetDisplayName(), - OperationType operationType => operationType.GetDisplayName(), - SecuritySchemeType securitySchemeType => securitySchemeType.GetDisplayName(), - _ => enumValue.ToString() - }; - } - /// /// Gets the enum display for name without the use of reflection. /// From 11466d76fe4687f7924a94584410d4b55e9afb9a Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 3 Jul 2024 23:26:50 -0700 Subject: [PATCH 03/19] Update PublicApi.Approved.txt --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 37f40bb11..f9c5e2b89 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -22,7 +22,8 @@ namespace Microsoft.OpenApi.Any public class OpenApiAnyCloneHelper { public OpenApiAnyCloneHelper() { } - public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(Microsoft.OpenApi.Any.IOpenApiAny obj) { } + public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(T obj) + where T : Microsoft.OpenApi.Any.IOpenApiAny { } } public class OpenApiArray : System.Collections.Generic.List, Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension { From a0b6f9d0be3ca9c70279e0f9e7f952ba7b5bb7c9 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 5 Jul 2024 19:12:55 -0700 Subject: [PATCH 04/19] Avoid binary compat break in CloneFromCopyConstructor --- .../Any/OpenApiAnyCloneHelper.cs | 23 +++++++++++++++++++ .../PublicApi/PublicApi.approved.txt | 1 + 2 files changed, 24 insertions(+) diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs index c2f76b08c..57c0826af 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs @@ -10,6 +10,29 @@ namespace Microsoft.OpenApi.Any /// public class OpenApiAnyCloneHelper { + /// + /// Clones an instance of object from the copy constructor + /// + /// The object instance. + /// A clone copy or the object itself. + public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) + { + if (obj != null) + { + var t = obj.GetType(); + foreach (var ci in t.GetConstructors()) + { + var pi = ci.GetParameters(); + if (pi.Length == 1 && pi[0].ParameterType == t) + { + return (IOpenApiAny)ci.Invoke(new object[] { obj }); + } + } + } + + return obj; + } + /// /// Clones an instance of object from the copy constructor /// diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index f9c5e2b89..759d441cc 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -22,6 +22,7 @@ namespace Microsoft.OpenApi.Any public class OpenApiAnyCloneHelper { public OpenApiAnyCloneHelper() { } + public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(Microsoft.OpenApi.Any.IOpenApiAny obj) { } public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(T obj) where T : Microsoft.OpenApi.Any.IOpenApiAny { } } From 5b006b2a6507fd8216604eaa352ac7d1328fcabe Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Mon, 8 Jul 2024 17:22:27 -0700 Subject: [PATCH 05/19] Add obsoletion attributes and fix CloneFromCopyConstructor --- src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs | 2 ++ src/Microsoft.OpenApi/Extensions/EnumExtensions.cs | 13 +++++++------ src/Microsoft.OpenApi/Models/OpenApiExample.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiMediaType.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiParameter.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 4 ++-- .../Models/RuntimeExpressionAnyWrapper.cs | 2 +- .../Attributes/DisplayAttributeTests.cs | 4 +++- .../PublicApi/PublicApi.approved.txt | 3 +++ 10 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs index 57c0826af..49699d930 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Diagnostics.CodeAnalysis; namespace Microsoft.OpenApi.Any @@ -15,6 +16,7 @@ public class OpenApiAnyCloneHelper /// /// The object instance. /// A clone copy or the object itself. + [Obsolete("Use native AoT-friendly generic overload of CloneFromCopyConstructor instead.")] public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) { if (obj != null) diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index 81fc65a1b..0e1a12e43 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -38,6 +38,7 @@ public static T GetAttributeOfType(this Enum enumValue) where T : Attribute /// Use if exists. /// Otherwise, use the standard string representation. /// + [Obsolete("Use native AoT-friendly type-specific overloads GetDisplayName methods instead.")] public static string GetDisplayName(this Enum enumValue) { var attribute = enumValue.GetAttributeOfType(); @@ -58,7 +59,7 @@ public static string GetDisplayName(this Enum enumValue) ParameterStyle.SpaceDelimited => "spaceDelimited", ParameterStyle.PipeDelimited => "pipeDelimited", ParameterStyle.DeepObject => "deepObject", - _ => parameterStyle.ToString() + _ => throw new InvalidOperationException($"Unknown parameter style: {parameterStyle}") }; /// @@ -66,13 +67,13 @@ public static string GetDisplayName(this Enum enumValue) /// /// The enum value. /// The display string to use. - internal static string GetDisplayName(this ParameterLocation parameterLocation) => parameterLocation switch + public static string GetDisplayName(this ParameterLocation parameterLocation) => parameterLocation switch { ParameterLocation.Query => "query", ParameterLocation.Header => "header", ParameterLocation.Path => "path", ParameterLocation.Cookie => "cookie", - _ => parameterLocation.ToString() + _ => throw new InvalidOperationException($"Unknown parameter location: {parameterLocation}") }; /// @@ -92,7 +93,7 @@ public static string GetDisplayName(this Enum enumValue) ReferenceType.Link => "links", ReferenceType.Callback => "callbacks", ReferenceType.Tag => "tags", - _ => referenceType.ToString() + _ => throw new InvalidOperationException($"Unknown reference type: {referenceType}") }; /// @@ -110,7 +111,7 @@ public static string GetDisplayName(this Enum enumValue) OperationType.Head => "head", OperationType.Patch => "patch", OperationType.Trace => "trace", - _ => operationType.ToString() + _ => throw new InvalidOperationException($"Unknown operation type: {operationType}") }; /// @@ -124,7 +125,7 @@ public static string GetDisplayName(this Enum enumValue) SecuritySchemeType.Http => "http", SecuritySchemeType.OAuth2 => "oauth2", SecuritySchemeType.OpenIdConnect => "openIdConnect", - _ => securitySchemeType.ToString() + _ => throw new InvalidOperationException($"Unknown security scheme type: {securitySchemeType}") }; } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index 1b9d31022..c78353127 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -66,7 +66,7 @@ public OpenApiExample(OpenApiExample example) { Summary = example?.Summary ?? Summary; Description = example?.Description ?? Description; - Value = OpenApiAnyCloneHelper.CloneFromCopyConstructor(example?.Value); + Value = OpenApiAnyCloneHelper.CloneFromCopyConstructor(example?.Value); ExternalValue = example?.ExternalValue ?? ExternalValue; Extensions = example?.Extensions != null ? new Dictionary(example.Extensions) : null; Reference = example?.Reference != null ? new(example?.Reference) : null; diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index 0e5fa4e8d..4093775a2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -106,7 +106,7 @@ public OpenApiHeader(OpenApiHeader header) Explode = header?.Explode ?? Explode; AllowReserved = header?.AllowReserved ?? AllowReserved; Schema = header?.Schema != null ? new(header?.Schema) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(header?.Example); + Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(header?.Example); Examples = header?.Examples != null ? new Dictionary(header.Examples) : null; Content = header?.Content != null ? new Dictionary(header.Content) : null; Extensions = header?.Extensions != null ? new Dictionary(header.Extensions) : null; diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index 444f41ba5..4751c4b28 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs @@ -54,7 +54,7 @@ public OpenApiMediaType() {} public OpenApiMediaType(OpenApiMediaType mediaType) { Schema = mediaType?.Schema != null ? new(mediaType?.Schema) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(mediaType?.Example); + Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(mediaType?.Example); Examples = mediaType?.Examples != null ? new Dictionary(mediaType.Examples) : null; Encoding = mediaType?.Encoding != null ? new Dictionary(mediaType.Encoding) : null; Extensions = mediaType?.Extensions != null ? new Dictionary(mediaType.Extensions) : null; diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index fc1eaf8cc..1cfbd2c6c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -166,7 +166,7 @@ public OpenApiParameter(OpenApiParameter parameter) AllowReserved = parameter?.AllowReserved ?? AllowReserved; Schema = parameter?.Schema != null ? new(parameter?.Schema) : null; Examples = parameter?.Examples != null ? new Dictionary(parameter.Examples) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(parameter?.Example); + Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(parameter?.Example); Content = parameter?.Content != null ? new Dictionary(parameter.Content) : null; Extensions = parameter?.Extensions != null ? new Dictionary(parameter.Extensions) : null; AllowEmptyValue = parameter?.AllowEmptyValue ?? AllowEmptyValue; diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index fd38927b6..40adf9a31 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -263,7 +263,7 @@ public OpenApiSchema(OpenApiSchema schema) MinLength = schema?.MinLength ?? MinLength; Pattern = schema?.Pattern ?? Pattern; MultipleOf = schema?.MultipleOf ?? MultipleOf; - Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); + Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); ReadOnly = schema?.ReadOnly ?? ReadOnly; WriteOnly = schema?.WriteOnly ?? WriteOnly; AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; @@ -281,7 +281,7 @@ public OpenApiSchema(OpenApiSchema schema) AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? AdditionalPropertiesAllowed; AdditionalProperties = schema?.AdditionalProperties != null ? new(schema?.AdditionalProperties) : null; Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); + Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); Enum = schema?.Enum != null ? new List(schema.Enum) : null; Nullable = schema?.Nullable ?? Nullable; ExternalDocs = schema?.ExternalDocs != null ? new(schema?.ExternalDocs) : null; diff --git a/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs b/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs index 1ef1aaaa1..796c72c3e 100644 --- a/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs +++ b/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs @@ -26,7 +26,7 @@ public RuntimeExpressionAnyWrapper() {} /// public RuntimeExpressionAnyWrapper(RuntimeExpressionAnyWrapper runtimeExpressionAnyWrapper) { - Any = OpenApiAnyCloneHelper.CloneFromCopyConstructor(runtimeExpressionAnyWrapper?.Any); + Any = OpenApiAnyCloneHelper.CloneFromCopyConstructor(runtimeExpressionAnyWrapper?.Any); Expression = runtimeExpressionAnyWrapper?.Expression; } diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index 274258601..7c3222d78 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -22,7 +22,9 @@ public class DisplayAttributeTests [InlineData(ApiLevel.Corporate, "corporate")] public void GetDisplayNameExtensionShouldUseDisplayAttribute(ApiLevel apiLevel, string expected) { - Assert.Equal(expected, apiLevel.GetDisplayName()); +#pragma warning disable CS0618 // Type or member is obsolete, testing obsolete behavior + Assert.Equal(expected, apiLevel.GetDisplayName()); +#pragma warning restore CS0618 // Type or member is obsolete, testing obsolete behavior } } } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 759d441cc..48db67ac0 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -22,6 +22,7 @@ namespace Microsoft.OpenApi.Any public class OpenApiAnyCloneHelper { public OpenApiAnyCloneHelper() { } + [System.Obsolete("Use native AoT-friendly generic overload of CloneFromCopyConstructor instead.")] public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(Microsoft.OpenApi.Any.IOpenApiAny obj) { } public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(T obj) where T : Microsoft.OpenApi.Any.IOpenApiAny { } @@ -250,6 +251,8 @@ namespace Microsoft.OpenApi.Extensions { public static T GetAttributeOfType(this System.Enum enumValue) where T : System.Attribute { } + public static string GetDisplayName(this Microsoft.OpenApi.Models.ParameterLocation parameterLocation) { } + [System.Obsolete("Use native AoT-friendly type-specific overloads GetDisplayName methods instead.")] public static string GetDisplayName(this System.Enum enumValue) { } } public static class OpenApiElementExtensions From 8ea0964d6f3e1ab6ea70cb1b7e925b227513288a Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Tue, 9 Jul 2024 13:52:00 -0700 Subject: [PATCH 06/19] Add ReferenceType.Path and tests for display names --- .../Extensions/EnumExtensions.cs | 1 + .../Attributes/DisplayAttributeTests.cs | 149 +++++++++++++++++- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index 0e1a12e43..1df069f35 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -93,6 +93,7 @@ public static string GetDisplayName(this Enum enumValue) ReferenceType.Link => "links", ReferenceType.Callback => "callbacks", ReferenceType.Tag => "tags", + ReferenceType.Path => "path", _ => throw new InvalidOperationException($"Unknown reference type: {referenceType}") }; diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index 7c3222d78..80d138bee 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -1,5 +1,10 @@ -using Microsoft.OpenApi.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using Microsoft.OpenApi.Attributes; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; using Xunit; namespace Microsoft.OpenApi.Tests.Attributes @@ -26,5 +31,147 @@ public void GetDisplayNameExtensionShouldUseDisplayAttribute(ApiLevel apiLevel, Assert.Equal(expected, apiLevel.GetDisplayName()); #pragma warning restore CS0618 // Type or member is obsolete, testing obsolete behavior } + + [Fact] + public void GetDisplayNameWorksForAllParameterStyle() + { + var enumValues = new List(Enum.GetValues()); + + Assert.Equal("matrix", ParameterStyle.Matrix.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.Matrix)); + + Assert.Equal("label", ParameterStyle.Label.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.Label)); + + Assert.Equal("form", ParameterStyle.Form.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.Form)); + + Assert.Equal("simple", ParameterStyle.Simple.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.Simple)); + + Assert.Equal("spaceDelimited", ParameterStyle.SpaceDelimited.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.SpaceDelimited)); + + Assert.Equal("pipeDelimited", ParameterStyle.PipeDelimited.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.PipeDelimited)); + + Assert.Equal("deepObject", ParameterStyle.DeepObject.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterStyle.DeepObject)); + + Assert.Empty(enumValues); + } + + [Fact] + public void GetDisplayNameWorksForAllParameterLocation() + { + var enumValues = new List(Enum.GetValues()); + + Assert.Equal("query", ParameterLocation.Query.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterLocation.Query)); + + Assert.Equal("header", ParameterLocation.Header.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterLocation.Header)); + + Assert.Equal("path", ParameterLocation.Path.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterLocation.Path)); + + Assert.Equal("cookie", ParameterLocation.Cookie.GetDisplayName()); + Assert.True(enumValues.Remove(ParameterLocation.Cookie)); + + Assert.Empty(enumValues); + } + + [Fact] + public void GetDisplayNameWorksForAllReferenceType() + { + var enumValues = new List(Enum.GetValues()); + + Assert.Equal("schemas", ReferenceType.Schema.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Schema)); + + Assert.Equal("responses", ReferenceType.Response.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Response)); + + Assert.Equal("parameters", ReferenceType.Parameter.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Parameter)); + + Assert.Equal("examples", ReferenceType.Example.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Example)); + + Assert.Equal("requestBodies", ReferenceType.RequestBody.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.RequestBody)); + + Assert.Equal("headers", ReferenceType.Header.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Header)); + + Assert.Equal("securitySchemes", ReferenceType.SecurityScheme.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.SecurityScheme)); + + Assert.Equal("links", ReferenceType.Link.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Link)); + + Assert.Equal("callbacks", ReferenceType.Callback.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Callback)); + + Assert.Equal("tags", ReferenceType.Tag.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Tag)); + + Assert.Equal("path", ReferenceType.Path.GetDisplayName()); + Assert.True(enumValues.Remove(ReferenceType.Path)); + + Assert.Empty(enumValues); + } + + [Fact] + public void GetDisplayNameWorksForAllOperationTypes() + { + var enumValues = new List(Enum.GetValues()); + + Assert.Equal("get", OperationType.Get.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Get)); + + Assert.Equal("put", OperationType.Put.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Put)); + + Assert.Equal("post", OperationType.Post.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Post)); + + Assert.Equal("delete", OperationType.Delete.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Delete)); + + Assert.Equal("options", OperationType.Options.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Options)); + + Assert.Equal("head", OperationType.Head.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Head)); + + Assert.Equal("patch", OperationType.Patch.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Patch)); + + Assert.Equal("trace", OperationType.Trace.GetDisplayName()); + Assert.True(enumValues.Remove(OperationType.Trace)); + + Assert.Empty(enumValues); + } + + [Fact] + public void GetDisplayNameWorksForAllSecuritySchemeTypes() + { + var enumValues = new List(Enum.GetValues()); + + Assert.Equal("apiKey", SecuritySchemeType.ApiKey.GetDisplayName()); + Assert.True(enumValues.Remove(SecuritySchemeType.ApiKey)); + + Assert.Equal("http", SecuritySchemeType.Http.GetDisplayName()); + Assert.True(enumValues.Remove(SecuritySchemeType.Http)); + + Assert.Equal("oauth2", SecuritySchemeType.OAuth2.GetDisplayName()); + Assert.True(enumValues.Remove(SecuritySchemeType.OAuth2)); + + Assert.Equal("openIdConnect", SecuritySchemeType.OpenIdConnect.GetDisplayName()); + Assert.True(enumValues.Remove(SecuritySchemeType.OpenIdConnect)); + + Assert.Empty(enumValues); + } } } From 212840898f980b7b4214fcd6a329012693fde1a7 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Mon, 15 Jul 2024 09:36:17 -0700 Subject: [PATCH 07/19] Guard ReferenceType consumption for ExternalResource --- src/Microsoft.OpenApi/Models/OpenApiReference.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index 339b9c632..86b165d65 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -200,8 +200,11 @@ private string GetExternalReferenceV3() { return ExternalResource + "#" + Id; } - - return ExternalResource + "#/components/" + Type.Value.GetDisplayName() + "/"+ Id; + + if (Type.HasValue) + { + return ExternalResource + "#/components/" + Type.Value.GetDisplayName() + "/"+ Id; + } } return ExternalResource; From bced7d5f93d323883c910bc9b788b19855dbcec5 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Tue, 16 Jul 2024 22:22:46 -0700 Subject: [PATCH 08/19] Add trimming test project and fix warnings --- .../Any/OpenApiAnyCloneHelper.cs | 1 + .../Attributes/TrimmingAttributes.cs | 280 ++++++++++++++++++ .../Extensions/EnumExtensions.cs | 3 + .../Extensions/StringExtensions.cs | 3 +- .../Validations/ValidationRuleSet.cs | 39 ++- .../Validations/ValidationRuleSetTests.cs | 23 ++ .../Microsoft.OpenApi.Trimming.Tests.csproj | 17 ++ .../Program.cs | 2 + 8 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj create mode 100644 test/Microsoft.OpenApi.Trimming.Tests/Program.cs diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs index 49699d930..ed224b7ef 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs @@ -17,6 +17,7 @@ public class OpenApiAnyCloneHelper /// The object instance. /// A clone copy or the object itself. [Obsolete("Use native AoT-friendly generic overload of CloneFromCopyConstructor instead.")] + [RequiresUnreferencedCode("CloneFromCopyConstructor is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of CloneFromCopyConstructor instead.")] public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) { if (obj != null) diff --git a/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs b/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs index 8c84377fd..538ed521e 100644 --- a/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs +++ b/src/Microsoft.OpenApi/Attributes/TrimmingAttributes.cs @@ -3,9 +3,289 @@ #nullable enable +// This collection of attribute definitions are helpers for accessing trim-related attributes in +// projects targeting .NET 6 or lower. Since the trimmer queries for these attributes by name, having +// these attributes source included is sufficient for the trimmer to recognize them. For more information +// on this approach, see https://devblogs.microsoft.com/dotnet/creating-aot-compatible-libraries/#approach-2-define-the-attributes-internally. namespace System.Diagnostics.CodeAnalysis { #if !NET7_0_OR_GREATER + /// + /// Indicates that the specified method requires the ability to generate new code at runtime, + /// for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when compiling ahead of time. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] + internal sealed class RequiresDynamicCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of dynamic code. + /// + public RequiresDynamicCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of dynamic code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires dynamic code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } + } +#endif + +#if !NET5_0_OR_GREATER + /// + /// Indicates that the specified method requires dynamic access to code that is not referenced + /// statically, for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when removing unreferenced + /// code from an application. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] + internal sealed class RequiresUnreferencedCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of unreferenced code. + /// + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } + } + + /// + /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a + /// single code artifact. + /// + /// + /// is different than + /// in that it doesn't have a + /// . So it is always preserved in the compiled assembly. + /// + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } + } + + /// + /// States a dependency that one member has on another. + /// + /// + /// This can be used to inform tooling of a dependency that is otherwise not evident purely from + /// metadata and IL, for example a member relied on via reflection. + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method, + AllowMultiple = true, Inherited = false)] + internal sealed class DynamicDependencyAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on the same type as the consumer. + /// + /// The signature of the member depended on. + public DynamicDependencyAttribute(string memberSignature) + { + MemberSignature = memberSignature; + } + + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on a . + /// + /// The signature of the member depended on. + /// The containing . + public DynamicDependencyAttribute(string memberSignature, Type type) + { + MemberSignature = memberSignature; + Type = type; + } + + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on a type in an assembly. + /// + /// The signature of the member depended on. + /// The full name of the type containing the specified member. + /// The assembly name of the type containing the specified member. + public DynamicDependencyAttribute(string memberSignature, string typeName, string assemblyName) + { + MemberSignature = memberSignature; + TypeName = typeName; + AssemblyName = assemblyName; + } + + /// + /// Initializes a new instance of the class + /// with the specified types of members on a . + /// + /// The types of members depended on. + /// The containing the specified members. + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) + { + MemberTypes = memberTypes; + Type = type; + } + + /// + /// Initializes a new instance of the class + /// with the specified types of members on a type in an assembly. + /// + /// The types of members depended on. + /// The full name of the type containing the specified members. + /// The assembly name of the type containing the specified members. + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName) + { + MemberTypes = memberTypes; + TypeName = typeName; + AssemblyName = assemblyName; + } + + /// + /// Gets the signature of the member depended on. + /// + /// + /// Either must be a valid string or + /// must not equal , but not both. + /// + public string? MemberSignature { get; } + + /// + /// Gets the which specifies the type + /// of members depended on. + /// + /// + /// Either must be a valid string or + /// must not equal , but not both. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + + /// + /// Gets the containing the specified member. + /// + /// + /// If neither nor are specified, + /// the type of the consumer is assumed. + /// + public Type? Type { get; } + + /// + /// Gets the full name of the type containing the specified member. + /// + /// + /// If neither nor are specified, + /// the type of the consumer is assumed. + /// + public string? TypeName { get; } + + /// + /// Gets the assembly name of the specified type. + /// + /// + /// is only valid when is specified. + /// + public string? AssemblyName { get; } + + /// + /// Gets or sets the condition in which the dependency is applicable, e.g. "DEBUG". + /// + public string? Condition { get; set; } + } + /// /// Indicates that certain members on a specified are accessed dynamically, /// for example through . diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index 1df069f35..362135d57 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Microsoft.OpenApi.Attributes; @@ -22,6 +23,7 @@ public static class EnumExtensions /// /// The attribute of the specified type or null. /// + [RequiresUnreferencedCode("GetAttributeOfType is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of GetDisplayName instead.")] public static T GetAttributeOfType(this Enum enumValue) where T : Attribute { var type = enumValue.GetType(); @@ -39,6 +41,7 @@ public static T GetAttributeOfType(this Enum enumValue) where T : Attribute /// Otherwise, use the standard string representation. /// [Obsolete("Use native AoT-friendly type-specific overloads GetDisplayName methods instead.")] + [RequiresUnreferencedCode("GetAttributeOfType is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of GetDisplayName instead.")] public static string GetDisplayName(this Enum enumValue) { var attribute = enumValue.GetAttributeOfType(); diff --git a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs index 51ce37365..01f54baea 100644 --- a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.OpenApi.Attributes; @@ -16,7 +17,7 @@ public static class StringExtensions /// Gets the enum value based on the given enum type and display name. /// /// The display name. - public static T GetEnumFromDisplayName(this string displayName) + public static T GetEnumFromDisplayName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(this string displayName) { var type = typeof(T); if (!type.IsEnum) diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index 3dd916755..104ec35de 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -185,17 +185,15 @@ IEnumerator IEnumerable.GetEnumerator() private static ValidationRuleSet BuildDefaultRuleSet() { var ruleSet = new ValidationRuleSet(); - var validationRuleType = typeof(ValidationRule); + + var ruleTypeProperties = GetValidationRuleTypes(); - var rules = typeof(ValidationRuleSet).Assembly.GetTypes() - .Where(t => t.IsClass - && t != typeof(object) - && t.GetCustomAttributes(typeof(OpenApiRuleAttribute), false).Any()) - .SelectMany(t2 => t2.GetProperties(BindingFlags.Static | BindingFlags.Public) - .Where(p => validationRuleType.IsAssignableFrom(p.PropertyType))); - - foreach (var property in rules) + foreach (var property in ruleTypeProperties) { + if (!typeof(ValidationRule).IsAssignableFrom(property.PropertyType)) + { + continue; + } var propertyValue = property.GetValue(null); // static property if (propertyValue is ValidationRule rule) { @@ -205,5 +203,28 @@ private static ValidationRuleSet BuildDefaultRuleSet() return ruleSet; } + + internal static PropertyInfo[] GetValidationRuleTypes() + { + return [ + ..typeof(OpenApiComponentsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiContactRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiDocumentRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiExtensibleRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiExternalDocsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiInfoRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiLicenseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiMediaTypeRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiOAuthFlowRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiServerRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiResponseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiResponsesRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiSchemaRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiHeaderRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiTagRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiPathsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ]; + } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs index 708d6ee64..3c302be13 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Linq; +using System.Reflection; +using Microsoft.OpenApi.Validations.Rules; using Xunit; namespace Microsoft.OpenApi.Validations.Tests @@ -37,5 +40,25 @@ public void DefaultRuleSetPropertyReturnsTheCorrectRules() // Update the number if you add new default rule(s). Assert.Equal(23, rules.Count); } + + [Fact] + public void GetValidationRuleTypesReturnsAllSupportedTypes() + { + var declaredRuleTypeProperties = typeof(ValidationRuleSet).Assembly.GetTypes() + .Where(t => t.IsClass + && t != typeof(object) + && t.GetCustomAttributes(typeof(OpenApiRuleAttribute), false).Any()) + .SelectMany(t2 => t2.GetProperties(BindingFlags.Static | BindingFlags.Public)) + .ToList(); + + var resolvedRuleTypeProperties = ValidationRuleSet.GetValidationRuleTypes(); + + foreach (var ruleTypeProperty in resolvedRuleTypeProperties) + { + Assert.True(declaredRuleTypeProperties.Remove(ruleTypeProperty)); + } + + Assert.Empty(declaredRuleTypeProperties); + } } } diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj new file mode 100644 index 000000000..e28b1ed96 --- /dev/null +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -0,0 +1,17 @@ + + + Exe + net8.0 + enable + enable + true + true + false + + + + + + + + diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Program.cs b/test/Microsoft.OpenApi.Trimming.Tests/Program.cs new file mode 100644 index 000000000..3751555cb --- /dev/null +++ b/test/Microsoft.OpenApi.Trimming.Tests/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); From be1046d8362e5a7f12d19445881190b8a639a8f1 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 17 Jul 2024 11:58:08 -0400 Subject: [PATCH 09/19] Apply suggestions from code review Co-authored-by: Eric Erhardt --- src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs | 2 +- .../Microsoft.OpenApi.Trimming.Tests.csproj | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs index ed224b7ef..65eb2b6f6 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs @@ -41,7 +41,7 @@ public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) /// /// The object instance. /// A clone copy or the object itself. - public static IOpenApiAny CloneFromCopyConstructor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(T obj) where T : IOpenApiAny + public static T CloneFromCopyConstructor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(T obj) where T : IOpenApiAny { if (obj != null) { diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index e28b1ed96..b4e3f4a36 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -4,7 +4,8 @@ net8.0 enable enable - true + true + false true false From 4cc030d19c063ce83a128b39513eddbb0a9661fe Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 17 Jul 2024 12:01:02 -0400 Subject: [PATCH 10/19] ci: adds trimming project to solution --- Microsoft.OpenApi.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Microsoft.OpenApi.sln b/Microsoft.OpenApi.sln index bb3c028e7..67f3f0e17 100644 --- a/Microsoft.OpenApi.sln +++ b/Microsoft.OpenApi.sln @@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Hidi", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OpenApi.Hidi.Tests", "test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj", "{D8F799DD-04AC-4A13-B344-45A5B944450A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OpenApi.Trimming.Tests", "test\Microsoft.OpenApi.Trimming.Tests\Microsoft.OpenApi.Trimming.Tests.csproj", "{1D2E0C6E-B103-4CB6-912E-D56FA1501296}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +70,10 @@ Global {D8F799DD-04AC-4A13-B344-45A5B944450A}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8F799DD-04AC-4A13-B344-45A5B944450A}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8F799DD-04AC-4A13-B344-45A5B944450A}.Release|Any CPU.Build.0 = Release|Any CPU + {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,6 +87,7 @@ Global {AD79B61D-88CF-497C-9ED5-41AE3867C5AC} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} {254841B5-7DAC-4D1D-A9C5-44FE5CE467BE} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1} {D8F799DD-04AC-4A13-B344-45A5B944450A} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} + {1D2E0C6E-B103-4CB6-912E-D56FA1501296} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F171EFC-0DB5-4B10-ABFA-AF48D52CC565} From 74275b80e8c132ac0c90476f8e1235735cc87769 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 17 Jul 2024 12:02:36 -0400 Subject: [PATCH 11/19] ci: adds readers project to trimming test project --- .../Microsoft.OpenApi.Trimming.Tests.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index b4e3f4a36..4bc040154 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -15,4 +15,8 @@ + + + + From 7f7c54ad8128baa1988f26793fbad99bad910bd6 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 17 Jul 2024 12:05:34 -0400 Subject: [PATCH 12/19] ci: adds missing trimming root assembly directive --- .../Microsoft.OpenApi.Trimming.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index 4bc040154..319531402 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -17,6 +17,7 @@ + From 06b54c377330c5a17a45bf4ec5f50bbdac6bab9e Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 17 Jul 2024 10:23:30 -0700 Subject: [PATCH 13/19] Fix build, address feedback, and remove Readers from trim tests --- src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs | 2 +- src/Microsoft.OpenApi/Extensions/EnumExtensions.cs | 1 + src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs | 3 ++- .../Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 3 ++- .../Microsoft.OpenApi.Trimming.Tests.csproj | 5 ----- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs index 65eb2b6f6..eaa1dac31 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs @@ -50,7 +50,7 @@ public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) var pi = ci.GetParameters(); if (pi.Length == 1 && pi[0].ParameterType == typeof(T)) { - return (IOpenApiAny)ci.Invoke([obj]); + return (T)ci.Invoke([obj]); } } } diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index 362135d57..280cdf719 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -23,6 +23,7 @@ public static class EnumExtensions /// /// The attribute of the specified type or null. /// + [Obsolete("GetAttributeOfType is deprecated and will be removed in a future release.")] [RequiresUnreferencedCode("GetAttributeOfType is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of GetDisplayName instead.")] public static T GetAttributeOfType(this Enum enumValue) where T : Attribute { diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index 104ec35de..448683fd9 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -185,12 +185,13 @@ IEnumerator IEnumerable.GetEnumerator() private static ValidationRuleSet BuildDefaultRuleSet() { var ruleSet = new ValidationRuleSet(); + var validationRuleType = typeof(ValidationRule); var ruleTypeProperties = GetValidationRuleTypes(); foreach (var property in ruleTypeProperties) { - if (!typeof(ValidationRule).IsAssignableFrom(property.PropertyType)) + if (!validationRuleType.IsAssignableFrom(property.PropertyType)) { continue; } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 48db67ac0..785ecbd52 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -24,7 +24,7 @@ namespace Microsoft.OpenApi.Any public OpenApiAnyCloneHelper() { } [System.Obsolete("Use native AoT-friendly generic overload of CloneFromCopyConstructor instead.")] public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(Microsoft.OpenApi.Any.IOpenApiAny obj) { } - public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(T obj) + public static T CloneFromCopyConstructor(T obj) where T : Microsoft.OpenApi.Any.IOpenApiAny { } } public class OpenApiArray : System.Collections.Generic.List, Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension @@ -249,6 +249,7 @@ namespace Microsoft.OpenApi.Extensions { public static class EnumExtensions { + [System.Obsolete("GetAttributeOfType is deprecated and will be removed in a future release.")] public static T GetAttributeOfType(this System.Enum enumValue) where T : System.Attribute { } public static string GetDisplayName(this Microsoft.OpenApi.Models.ParameterLocation parameterLocation) { } diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index 319531402..b4e3f4a36 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -15,9 +15,4 @@ - - - - - From c117bc56f17317853763b065aac943a1eedee9f7 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 18 Jul 2024 11:59:18 -0700 Subject: [PATCH 14/19] Rely on enum fields never being trimmed --- .../Extensions/EnumExtensions.cs | 92 +---------- .../Extensions/StringExtensions.cs | 7 +- .../Attributes/DisplayAttributeTests.cs | 153 +----------------- .../PublicApi/PublicApi.approved.txt | 3 - 4 files changed, 7 insertions(+), 248 deletions(-) diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index 280cdf719..1eadaa5f4 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Reflection; using Microsoft.OpenApi.Attributes; -using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Extensions { @@ -23,12 +22,11 @@ public static class EnumExtensions /// /// The attribute of the specified type or null. /// - [Obsolete("GetAttributeOfType is deprecated and will be removed in a future release.")] - [RequiresUnreferencedCode("GetAttributeOfType is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of GetDisplayName instead.")] + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Fields are never trimmed for enum types.")] public static T GetAttributeOfType(this Enum enumValue) where T : Attribute { var type = enumValue.GetType(); - var memInfo = type.GetMember(enumValue.ToString()).First(); + var memInfo = type.GetField(enumValue.ToString(), BindingFlags.Public | BindingFlags.Static); var attributes = memInfo.GetCustomAttributes(false); return attributes.FirstOrDefault(); } @@ -41,96 +39,10 @@ public static T GetAttributeOfType(this Enum enumValue) where T : Attribute /// Use if exists. /// Otherwise, use the standard string representation. /// - [Obsolete("Use native AoT-friendly type-specific overloads GetDisplayName methods instead.")] - [RequiresUnreferencedCode("GetAttributeOfType is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of GetDisplayName instead.")] public static string GetDisplayName(this Enum enumValue) { var attribute = enumValue.GetAttributeOfType(); return attribute == null ? enumValue.ToString() : attribute.Name; } - - /// - /// Gets the enum display for name without the use of reflection. - /// - /// The enum value. - /// The display string to use. - internal static string GetDisplayName(this ParameterStyle parameterStyle) => parameterStyle switch - { - ParameterStyle.Matrix => "matrix", - ParameterStyle.Label => "label", - ParameterStyle.Form => "form", - ParameterStyle.Simple => "simple", - ParameterStyle.SpaceDelimited => "spaceDelimited", - ParameterStyle.PipeDelimited => "pipeDelimited", - ParameterStyle.DeepObject => "deepObject", - _ => throw new InvalidOperationException($"Unknown parameter style: {parameterStyle}") - }; - - /// - /// Gets the enum display for name without the use of reflection. - /// - /// The enum value. - /// The display string to use. - public static string GetDisplayName(this ParameterLocation parameterLocation) => parameterLocation switch - { - ParameterLocation.Query => "query", - ParameterLocation.Header => "header", - ParameterLocation.Path => "path", - ParameterLocation.Cookie => "cookie", - _ => throw new InvalidOperationException($"Unknown parameter location: {parameterLocation}") - }; - - /// - /// Gets the enum display for name without the use of reflection. - /// - /// The enum value. - /// The display string to use. - internal static string GetDisplayName(this ReferenceType referenceType) => referenceType switch - { - ReferenceType.Schema => "schemas", - ReferenceType.Response => "responses", - ReferenceType.Parameter => "parameters", - ReferenceType.Example => "examples", - ReferenceType.RequestBody => "requestBodies", - ReferenceType.Header => "headers", - ReferenceType.SecurityScheme => "securitySchemes", - ReferenceType.Link => "links", - ReferenceType.Callback => "callbacks", - ReferenceType.Tag => "tags", - ReferenceType.Path => "path", - _ => throw new InvalidOperationException($"Unknown reference type: {referenceType}") - }; - - /// - /// Gets the enum display for name without the use of reflection. - /// - /// The enum value. - /// The display string to use. - internal static string GetDisplayName(this OperationType operationType) => operationType switch - { - OperationType.Get => "get", - OperationType.Put => "put", - OperationType.Post => "post", - OperationType.Delete => "delete", - OperationType.Options => "options", - OperationType.Head => "head", - OperationType.Patch => "patch", - OperationType.Trace => "trace", - _ => throw new InvalidOperationException($"Unknown operation type: {operationType}") - }; - - /// - /// Gets the enum display for name without the use of reflection. - /// - /// The enum value. - /// The display string to use. - internal static string GetDisplayName(this SecuritySchemeType securitySchemeType) => securitySchemeType switch - { - SecuritySchemeType.ApiKey => "apiKey", - SecuritySchemeType.Http => "http", - SecuritySchemeType.OAuth2 => "oauth2", - SecuritySchemeType.OpenIdConnect => "openIdConnect", - _ => throw new InvalidOperationException($"Unknown security scheme type: {securitySchemeType}") - }; } } diff --git a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs index 01f54baea..3fa3f6b52 100644 --- a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs @@ -17,6 +17,7 @@ public static class StringExtensions /// Gets the enum value based on the given enum type and display name. /// /// The display name. + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Fields are never trimmed for enum types.")] public static T GetEnumFromDisplayName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(this string displayName) { var type = typeof(T); @@ -25,14 +26,12 @@ public static class StringExtensions return default; } - foreach (var value in Enum.GetValues(type)) + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static)) { - var field = type.GetField(value.ToString()); - var displayAttribute = (DisplayAttribute)field.GetCustomAttribute(typeof(DisplayAttribute)); if (displayAttribute != null && displayAttribute.Name == displayName) { - return (T)value; + return (T)field.GetValue(null); } } diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index 80d138bee..274258601 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata; -using Microsoft.OpenApi.Attributes; +using Microsoft.OpenApi.Attributes; using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; using Xunit; namespace Microsoft.OpenApi.Tests.Attributes @@ -27,151 +22,7 @@ public class DisplayAttributeTests [InlineData(ApiLevel.Corporate, "corporate")] public void GetDisplayNameExtensionShouldUseDisplayAttribute(ApiLevel apiLevel, string expected) { -#pragma warning disable CS0618 // Type or member is obsolete, testing obsolete behavior - Assert.Equal(expected, apiLevel.GetDisplayName()); -#pragma warning restore CS0618 // Type or member is obsolete, testing obsolete behavior - } - - [Fact] - public void GetDisplayNameWorksForAllParameterStyle() - { - var enumValues = new List(Enum.GetValues()); - - Assert.Equal("matrix", ParameterStyle.Matrix.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.Matrix)); - - Assert.Equal("label", ParameterStyle.Label.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.Label)); - - Assert.Equal("form", ParameterStyle.Form.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.Form)); - - Assert.Equal("simple", ParameterStyle.Simple.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.Simple)); - - Assert.Equal("spaceDelimited", ParameterStyle.SpaceDelimited.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.SpaceDelimited)); - - Assert.Equal("pipeDelimited", ParameterStyle.PipeDelimited.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.PipeDelimited)); - - Assert.Equal("deepObject", ParameterStyle.DeepObject.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterStyle.DeepObject)); - - Assert.Empty(enumValues); - } - - [Fact] - public void GetDisplayNameWorksForAllParameterLocation() - { - var enumValues = new List(Enum.GetValues()); - - Assert.Equal("query", ParameterLocation.Query.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterLocation.Query)); - - Assert.Equal("header", ParameterLocation.Header.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterLocation.Header)); - - Assert.Equal("path", ParameterLocation.Path.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterLocation.Path)); - - Assert.Equal("cookie", ParameterLocation.Cookie.GetDisplayName()); - Assert.True(enumValues.Remove(ParameterLocation.Cookie)); - - Assert.Empty(enumValues); - } - - [Fact] - public void GetDisplayNameWorksForAllReferenceType() - { - var enumValues = new List(Enum.GetValues()); - - Assert.Equal("schemas", ReferenceType.Schema.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Schema)); - - Assert.Equal("responses", ReferenceType.Response.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Response)); - - Assert.Equal("parameters", ReferenceType.Parameter.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Parameter)); - - Assert.Equal("examples", ReferenceType.Example.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Example)); - - Assert.Equal("requestBodies", ReferenceType.RequestBody.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.RequestBody)); - - Assert.Equal("headers", ReferenceType.Header.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Header)); - - Assert.Equal("securitySchemes", ReferenceType.SecurityScheme.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.SecurityScheme)); - - Assert.Equal("links", ReferenceType.Link.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Link)); - - Assert.Equal("callbacks", ReferenceType.Callback.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Callback)); - - Assert.Equal("tags", ReferenceType.Tag.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Tag)); - - Assert.Equal("path", ReferenceType.Path.GetDisplayName()); - Assert.True(enumValues.Remove(ReferenceType.Path)); - - Assert.Empty(enumValues); - } - - [Fact] - public void GetDisplayNameWorksForAllOperationTypes() - { - var enumValues = new List(Enum.GetValues()); - - Assert.Equal("get", OperationType.Get.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Get)); - - Assert.Equal("put", OperationType.Put.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Put)); - - Assert.Equal("post", OperationType.Post.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Post)); - - Assert.Equal("delete", OperationType.Delete.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Delete)); - - Assert.Equal("options", OperationType.Options.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Options)); - - Assert.Equal("head", OperationType.Head.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Head)); - - Assert.Equal("patch", OperationType.Patch.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Patch)); - - Assert.Equal("trace", OperationType.Trace.GetDisplayName()); - Assert.True(enumValues.Remove(OperationType.Trace)); - - Assert.Empty(enumValues); - } - - [Fact] - public void GetDisplayNameWorksForAllSecuritySchemeTypes() - { - var enumValues = new List(Enum.GetValues()); - - Assert.Equal("apiKey", SecuritySchemeType.ApiKey.GetDisplayName()); - Assert.True(enumValues.Remove(SecuritySchemeType.ApiKey)); - - Assert.Equal("http", SecuritySchemeType.Http.GetDisplayName()); - Assert.True(enumValues.Remove(SecuritySchemeType.Http)); - - Assert.Equal("oauth2", SecuritySchemeType.OAuth2.GetDisplayName()); - Assert.True(enumValues.Remove(SecuritySchemeType.OAuth2)); - - Assert.Equal("openIdConnect", SecuritySchemeType.OpenIdConnect.GetDisplayName()); - Assert.True(enumValues.Remove(SecuritySchemeType.OpenIdConnect)); - - Assert.Empty(enumValues); + Assert.Equal(expected, apiLevel.GetDisplayName()); } } } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 785ecbd52..82c5f6a88 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -249,11 +249,8 @@ namespace Microsoft.OpenApi.Extensions { public static class EnumExtensions { - [System.Obsolete("GetAttributeOfType is deprecated and will be removed in a future release.")] public static T GetAttributeOfType(this System.Enum enumValue) where T : System.Attribute { } - public static string GetDisplayName(this Microsoft.OpenApi.Models.ParameterLocation parameterLocation) { } - [System.Obsolete("Use native AoT-friendly type-specific overloads GetDisplayName methods instead.")] public static string GetDisplayName(this System.Enum enumValue) { } } public static class OpenApiElementExtensions From 35811cc51ef23845e30710de0269fdec13f51c47 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 19 Jul 2024 10:18:59 -0700 Subject: [PATCH 15/19] Address feedback --- .../Extensions/StringExtensions.cs | 1 - .../Attributes/DisplayAttributeTests.cs | 32 ++++++++++++++++++- .../Program.cs | 3 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs index 3fa3f6b52..541523df5 100644 --- a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs @@ -17,7 +17,6 @@ public static class StringExtensions /// Gets the enum value based on the given enum type and display name. /// /// The display name. - [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Fields are never trimmed for enum types.")] public static T GetEnumFromDisplayName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(this string displayName) { var type = typeof(T); diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index 274258601..0035ffd02 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -1,4 +1,5 @@ -using Microsoft.OpenApi.Attributes; +using System; +using Microsoft.OpenApi.Attributes; using Microsoft.OpenApi.Extensions; using Xunit; @@ -14,6 +15,17 @@ public enum ApiLevel Corporate = 3 } + [Flags] + public enum UserType + { + [DisplayAttribute("admin")] + Admin = 1, + [DisplayAttribute("editor")] + Editor = 2, + [DisplayAttribute("publisher")] + Publisher = 3 + } + public class DisplayAttributeTests { [Theory] @@ -24,5 +36,23 @@ public void GetDisplayNameExtensionShouldUseDisplayAttribute(ApiLevel apiLevel, { Assert.Equal(expected, apiLevel.GetDisplayName()); } + + [Theory] + [InlineData(ApiLevel.Private,"private")] + [InlineData(ApiLevel.Public, "public")] + [InlineData(ApiLevel.Corporate, "corporate")] + public void GetEnumFromDisplayNameShouldReturnEnumValue(ApiLevel expected, string displayName) + { + Assert.Equal(expected, displayName.GetEnumFromDisplayName()); + } + + [Theory] + [InlineData(UserType.Admin,"admin")] + [InlineData(UserType.Publisher, "publisher")] + [InlineData(UserType.Editor, "editor")] + public void GetEnumFromDisplayNameShouldReturnEnumValueForFlagsEnum(UserType expected, string displayName) + { + Assert.Equal(expected, displayName.GetEnumFromDisplayName()); + } } } diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Program.cs b/test/Microsoft.OpenApi.Trimming.Tests/Program.cs index 3751555cb..1bc52a60a 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Program.cs +++ b/test/Microsoft.OpenApi.Trimming.Tests/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); From 8267c4042728f47e0d6096e09e0df1c029d5aa01 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 19 Jul 2024 10:20:18 -0700 Subject: [PATCH 16/19] Add back doc comment on M.O.Readers --- .../Microsoft.OpenApi.Trimming.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index b4e3f4a36..3e6daf74c 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -13,6 +13,7 @@ + From 620076775a00c495a0ae06d0bd3dc99b1225a97a Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Jul 2024 11:58:55 +0300 Subject: [PATCH 17/19] Update test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs Co-authored-by: Eric Erhardt --- .../Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index 0035ffd02..b37865565 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -24,6 +24,8 @@ public enum UserType Editor = 2, [DisplayAttribute("publisher")] Publisher = 3 + [DisplayAttribute("all")] + All = Admin | Editor | Publisher } public class DisplayAttributeTests From 47ad39db0a318b7e1751d2bab6fe71c764a0601e Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Mon, 22 Jul 2024 12:20:25 +0300 Subject: [PATCH 18/19] Fix syntax error --- .../Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index b37865565..f9bb8beff 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -23,7 +23,7 @@ public enum UserType [DisplayAttribute("editor")] Editor = 2, [DisplayAttribute("publisher")] - Publisher = 3 + Publisher = 3, [DisplayAttribute("all")] All = Admin | Editor | Publisher } From 321e20d7b8c1897cb1fc5453c0cfd55a5ed9f31f Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Mon, 22 Jul 2024 12:48:45 +0300 Subject: [PATCH 19/19] Use native AOT-friendly generic overload --- src/Microsoft.OpenApi/Any/OpenApiArray.cs | 2 +- src/Microsoft.OpenApi/Any/OpenApiObject.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Any/OpenApiArray.cs b/src/Microsoft.OpenApi/Any/OpenApiArray.cs index 461e2284a..5a9af0fff 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiArray.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiArray.cs @@ -29,7 +29,7 @@ public OpenApiArray(OpenApiArray array) AnyType = array.AnyType; foreach (var item in array) { - Add(OpenApiAnyCloneHelper.CloneFromCopyConstructor(item)); + Add(OpenApiAnyCloneHelper.CloneFromCopyConstructor(item)); } } diff --git a/src/Microsoft.OpenApi/Any/OpenApiObject.cs b/src/Microsoft.OpenApi/Any/OpenApiObject.cs index 60cfc75c3..95783cc23 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiObject.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiObject.cs @@ -29,7 +29,7 @@ public OpenApiObject(OpenApiObject obj) AnyType = obj.AnyType; foreach (var key in obj.Keys) { - this[key] = OpenApiAnyCloneHelper.CloneFromCopyConstructor(obj[key]); + this[key] = OpenApiAnyCloneHelper.CloneFromCopyConstructor(obj[key]); } }