diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiAnnotatable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiAnnotatable.cs new file mode 100644 index 000000000..dc1ee84a0 --- /dev/null +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiAnnotatable.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.OpenApi.Interfaces +{ + /// + /// Represents an Open API element that can be annotated with + /// non-serializable properties in a property bag. + /// + public interface IOpenApiAnnotatable + { + /// + /// A collection of properties associated with the current OpenAPI element. + /// + IDictionary Annotations { get; set; } + } +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 8d4526a20..712bddb03 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -17,7 +17,7 @@ namespace Microsoft.OpenApi.Models /// /// Describes an OpenAPI object (OpenAPI document). See: https://swagger.io/specification /// - public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible + public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable { /// /// Related workspace containing OpenApiDocuments that are referenced in this document @@ -70,6 +70,9 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible /// public string HashCode => GenerateHashValue(this); + /// + public IDictionary Annotations { get; set; } + /// /// Parameter-less constructor /// @@ -89,6 +92,7 @@ public OpenApiDocument(OpenApiDocument document) Tags = document?.Tags != null ? new List(document.Tags) : null; ExternalDocs = document?.ExternalDocs != null ? new(document?.ExternalDocs) : null; Extensions = document?.Extensions != null ? new Dictionary(document.Extensions) : null; + Annotations = document?.Annotations != null ? new Dictionary(document.Annotations) : null; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 360cfe7c1..69054740e 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models /// /// Operation Object. /// - public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible + public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable { /// /// Default value for . @@ -105,6 +105,9 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible /// public IDictionary Extensions { get; set; } = new Dictionary(); + /// + public IDictionary Annotations { get; set; } + /// /// Parameterless constructor /// @@ -128,6 +131,7 @@ public OpenApiOperation(OpenApiOperation operation) Security = operation?.Security != null ? new List(operation.Security) : null; Servers = operation?.Servers != null ? new List(operation.Servers) : null; Extensions = operation?.Extensions != null ? new Dictionary(operation.Extensions) : null; + Annotations = operation?.Annotations != null ? new Dictionary(operation.Annotations) : null; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 40adf9a31..957e0f946 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models /// /// Schema Object. /// - public class OpenApiSchema : IOpenApiReferenceable, IEffective, IOpenApiExtensible + public class OpenApiSchema : IOpenApiReferenceable, IEffective, IOpenApiExtensible, IOpenApiAnnotatable { /// /// Follow JSON Schema definition. Short text providing information about the data. @@ -241,6 +241,9 @@ public class OpenApiSchema : IOpenApiReferenceable, IEffective, I /// public OpenApiReference Reference { get; set; } + /// + public IDictionary Annotations { get; set; } + /// /// Parameterless constructor /// @@ -290,6 +293,7 @@ public OpenApiSchema(OpenApiSchema schema) Extensions = schema?.Extensions != null ? new Dictionary(schema.Extensions) : null; UnresolvedReference = schema?.UnresolvedReference ?? UnresolvedReference; Reference = schema?.Reference != null ? new(schema?.Reference) : null; + Annotations = schema?.Annotations != null ? new Dictionary(schema?.Annotations) : null; } /// diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 7a7f883f6..168ec3ade 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -29,7 +29,8 @@ public class OpenApiDocumentTests { Type = ReferenceType.Schema, Id = "schema2" - } + }, + Annotations = new Dictionary { { "x-foo", "bar" } } }, ["schema2"] = new() { @@ -38,7 +39,8 @@ public class OpenApiDocumentTests { ["property1"] = new() { - Type = "string" + Type = "string", + Annotations = new Dictionary { { "key1", "value" } } } } }, @@ -56,9 +58,11 @@ public class OpenApiDocumentTests { ["property1"] = new() { - Type = "string" + Type = "string", + Annotations = new Dictionary { { "key1", "value" } } } }, + Annotations = new Dictionary { { "key1", "value" } }, Reference = new() { Type = ReferenceType.Schema, @@ -100,6 +104,7 @@ public class OpenApiDocumentTests { Version = "1.0.0" }, + Annotations = new Dictionary { { "key1", "value" } }, Components = TopLevelReferencingComponents }; @@ -109,6 +114,7 @@ public class OpenApiDocumentTests { Version = "1.0.0" }, + Annotations = new Dictionary { { "key1", "value" } }, Components = TopLevelSelfReferencingComponentsWithOtherProperties }; @@ -118,6 +124,7 @@ public class OpenApiDocumentTests { Version = "1.0.0" }, + Annotations = new Dictionary { { "key1", "value" } }, Components = TopLevelSelfReferencingComponents }; @@ -509,6 +516,7 @@ public class OpenApiDocumentTests } } }, + Annotations = new Dictionary { { "key1", "value" } }, Components = AdvancedComponentsWithReference }; @@ -884,6 +892,7 @@ public class OpenApiDocumentTests } } }, + Annotations = new Dictionary { { "key1", "value" } }, Components = AdvancedComponents }; @@ -1272,6 +1281,7 @@ public class OpenApiDocumentTests } } }, + Annotations = new Dictionary { { "key1", "value" } }, Components = AdvancedComponents }; @@ -1827,5 +1837,26 @@ public void SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); } + + [Fact] + public void OpenApiDocumentCopyConstructorWithAnnotationsSucceeds() + { + var baseDocument = new OpenApiDocument + { + Annotations = new Dictionary + { + ["key1"] = "value1", + ["key2"] = 2 + } + }; + + var actualDocument = new OpenApiDocument(baseDocument); + + Assert.Equal(baseDocument.Annotations["key1"], actualDocument.Annotations["key1"]); + + baseDocument.Annotations["key1"] = "value2"; + + Assert.NotEqual(baseDocument.Annotations["key1"], actualDocument.Annotations["key1"]); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index ec6ca8326..52906e61c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -87,7 +87,8 @@ public class OpenApiOperationTests Url = "http://server.com", Description = "serverDescription" } - } + }, + Annotations = new Dictionary { { "key1", "value1" }, { "key2", 2 } }, }; private static readonly OpenApiOperation _advancedOperationWithTagsAndSecurity = new() @@ -864,5 +865,26 @@ public void EnsureOpenApiOperationCopyConstructor_SerializationResultsInSame() actual.Should().Be(expected); } } + + [Fact] + public void OpenApiOperationCopyConstructorWithAnnotationsSucceeds() + { + var baseOperation = new OpenApiOperation + { + Annotations = new Dictionary + { + ["key1"] = "value1", + ["key2"] = 2 + } + }; + + var actualOperation = new OpenApiOperation(baseOperation); + + Assert.Equal(baseOperation.Annotations["key1"], actualOperation.Annotations["key1"]); + + baseOperation.Annotations["key1"] = "value2"; + + Assert.NotEqual(baseOperation.Annotations["key1"], actualOperation.Annotations["key1"]); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 0acd55925..1906dbbc4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -36,7 +36,8 @@ public class OpenApiSchemaTests ExternalDocs = new() { Url = new("http://example.com/externalDocs") - } + }, + Annotations = new Dictionary { { "key1", "value1" }, { "key2", 2 } } }; public static readonly OpenApiSchema AdvancedSchemaObject = new() @@ -483,6 +484,27 @@ public void OpenApiSchemaCopyConstructorSucceeds() Assert.True(actualSchema.Nullable); } + [Fact] + public void OpenApiSchemaCopyConstructorWithAnnotationsSucceeds() + { + var baseSchema = new OpenApiSchema + { + Annotations = new Dictionary + { + ["key1"] = "value1", + ["key2"] = 2 + } + }; + + var actualSchema = new OpenApiSchema(baseSchema); + + Assert.Equal(baseSchema.Annotations["key1"], actualSchema.Annotations["key1"]); + + baseSchema.Annotations["key1"] = "value2"; + + Assert.NotEqual(baseSchema.Annotations["key1"], actualSchema.Annotations["key1"]); + } + public static TheoryData SchemaExamples() { return new() diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 82c5f6a88..fbfe564f3 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -302,6 +302,10 @@ namespace Microsoft.OpenApi.Interfaces { T GetEffective(Microsoft.OpenApi.Models.OpenApiDocument document); } + public interface IOpenApiAnnotatable + { + System.Collections.Generic.IDictionary Annotations { get; set; } + } public interface IOpenApiElement { } public interface IOpenApiExtensible : Microsoft.OpenApi.Interfaces.IOpenApiElement { @@ -592,10 +596,11 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiDocument : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiDocument : Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiDocument() { } public OpenApiDocument(Microsoft.OpenApi.Models.OpenApiDocument document) { } + public System.Collections.Generic.IDictionary Annotations { get; set; } public Microsoft.OpenApi.Models.OpenApiComponents Components { get; set; } public System.Collections.Generic.IDictionary Extensions { get; set; } public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } @@ -774,11 +779,12 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiOperation : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiOperation : Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public const bool DeprecatedDefault = false; public OpenApiOperation() { } public OpenApiOperation(Microsoft.OpenApi.Models.OpenApiOperation operation) { } + public System.Collections.Generic.IDictionary Annotations { get; set; } public System.Collections.Generic.IDictionary Callbacks { get; set; } public bool Deprecated { get; set; } public string Description { get; set; } @@ -900,13 +906,14 @@ namespace Microsoft.OpenApi.Models public OpenApiResponses() { } public OpenApiResponses(Microsoft.OpenApi.Models.OpenApiResponses openApiResponses) { } } - public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSchema() { } public OpenApiSchema(Microsoft.OpenApi.Models.OpenApiSchema schema) { } public Microsoft.OpenApi.Models.OpenApiSchema AdditionalProperties { get; set; } public bool AdditionalPropertiesAllowed { get; set; } public System.Collections.Generic.IList AllOf { get; set; } + public System.Collections.Generic.IDictionary Annotations { get; set; } public System.Collections.Generic.IList AnyOf { get; set; } public Microsoft.OpenApi.Any.IOpenApiAny Default { get; set; } public bool Deprecated { get; set; }