Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for unserializable annotations on OpenAPI document #1769

Merged
merged 1 commit into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/Microsoft.OpenApi/Interfaces/IOpenApiAnnotatable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;

namespace Microsoft.OpenApi.Interfaces
{
/// <summary>
/// Represents an Open API element that can be annotated with
/// non-serializable properties in a property bag.
/// </summary>
public interface IOpenApiAnnotatable
{
/// <summary>
/// A collection of properties associated with the current OpenAPI element.
/// </summary>
IDictionary<string, object> Annotations { get; set; }
}
}
6 changes: 5 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.OpenApi.Models
/// <summary>
/// Describes an OpenAPI object (OpenAPI document). See: https://swagger.io/specification
/// </summary>
public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable
{
/// <summary>
/// Related workspace containing OpenApiDocuments that are referenced in this document
Expand Down Expand Up @@ -70,6 +70,9 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public string HashCode => GenerateHashValue(this);

/// <inheritdoc />
public IDictionary<string, object> Annotations { get; set; }

/// <summary>
/// Parameter-less constructor
/// </summary>
Expand All @@ -89,6 +92,7 @@ public OpenApiDocument(OpenApiDocument document)
Tags = document?.Tags != null ? new List<OpenApiTag>(document.Tags) : null;
ExternalDocs = document?.ExternalDocs != null ? new(document?.ExternalDocs) : null;
Extensions = document?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(document.Extensions) : null;
Annotations = document?.Annotations != null ? new Dictionary<string, object>(document.Annotations) : null;
}

/// <summary>
Expand Down
6 changes: 5 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models
/// <summary>
/// Operation Object.
/// </summary>
public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible
public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable
{
/// <summary>
/// Default value for <see cref="Deprecated"/>.
Expand Down Expand Up @@ -105,6 +105,9 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <inheritdoc />
public IDictionary<string, object> Annotations { get; set; }

/// <summary>
/// Parameterless constructor
/// </summary>
Expand All @@ -128,6 +131,7 @@ public OpenApiOperation(OpenApiOperation operation)
Security = operation?.Security != null ? new List<OpenApiSecurityRequirement>(operation.Security) : null;
Servers = operation?.Servers != null ? new List<OpenApiServer>(operation.Servers) : null;
Extensions = operation?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(operation.Extensions) : null;
Annotations = operation?.Annotations != null ? new Dictionary<string, object>(operation.Annotations) : null;
}

/// <summary>
Expand Down
6 changes: 5 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models
/// <summary>
/// Schema Object.
/// </summary>
public class OpenApiSchema : IOpenApiReferenceable, IEffective<OpenApiSchema>, IOpenApiExtensible
public class OpenApiSchema : IOpenApiReferenceable, IEffective<OpenApiSchema>, IOpenApiExtensible, IOpenApiAnnotatable
{
/// <summary>
/// Follow JSON Schema definition. Short text providing information about the data.
Expand Down Expand Up @@ -241,6 +241,9 @@ public class OpenApiSchema : IOpenApiReferenceable, IEffective<OpenApiSchema>, I
/// </summary>
public OpenApiReference Reference { get; set; }

/// <inheritdoc />
public IDictionary<string, object> Annotations { get; set; }

/// <summary>
/// Parameterless constructor
/// </summary>
Expand Down Expand Up @@ -290,6 +293,7 @@ public OpenApiSchema(OpenApiSchema schema)
Extensions = schema?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(schema.Extensions) : null;
UnresolvedReference = schema?.UnresolvedReference ?? UnresolvedReference;
Reference = schema?.Reference != null ? new(schema?.Reference) : null;
Annotations = schema?.Annotations != null ? new Dictionary<string, object>(schema?.Annotations) : null;
}

/// <summary>
Expand Down
37 changes: 34 additions & 3 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public class OpenApiDocumentTests
{
Type = ReferenceType.Schema,
Id = "schema2"
}
},
Annotations = new Dictionary<string, object> { { "x-foo", "bar" } }
},
["schema2"] = new()
{
Expand All @@ -38,7 +39,8 @@ public class OpenApiDocumentTests
{
["property1"] = new()
{
Type = "string"
Type = "string",
Annotations = new Dictionary<string, object> { { "key1", "value" } }
}
}
},
Expand All @@ -56,9 +58,11 @@ public class OpenApiDocumentTests
{
["property1"] = new()
{
Type = "string"
Type = "string",
Annotations = new Dictionary<string, object> { { "key1", "value" } }
}
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Reference = new()
{
Type = ReferenceType.Schema,
Expand Down Expand Up @@ -100,6 +104,7 @@ public class OpenApiDocumentTests
{
Version = "1.0.0"
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Components = TopLevelReferencingComponents
};

Expand All @@ -109,6 +114,7 @@ public class OpenApiDocumentTests
{
Version = "1.0.0"
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Components = TopLevelSelfReferencingComponentsWithOtherProperties
};

Expand All @@ -118,6 +124,7 @@ public class OpenApiDocumentTests
{
Version = "1.0.0"
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Components = TopLevelSelfReferencingComponents
};

Expand Down Expand Up @@ -509,6 +516,7 @@ public class OpenApiDocumentTests
}
}
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Components = AdvancedComponentsWithReference
};

Expand Down Expand Up @@ -884,6 +892,7 @@ public class OpenApiDocumentTests
}
}
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Components = AdvancedComponents
};

Expand Down Expand Up @@ -1272,6 +1281,7 @@ public class OpenApiDocumentTests
}
}
},
Annotations = new Dictionary<string, object> { { "key1", "value" } },
Components = AdvancedComponents
};

Expand Down Expand Up @@ -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<string, object>
{
["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"]);
}
}
}
24 changes: 23 additions & 1 deletion test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public class OpenApiOperationTests
Url = "http://server.com",
Description = "serverDescription"
}
}
},
Annotations = new Dictionary<string, object> { { "key1", "value1" }, { "key2", 2 } },
};

private static readonly OpenApiOperation _advancedOperationWithTagsAndSecurity = new()
Expand Down Expand Up @@ -864,5 +865,26 @@ public void EnsureOpenApiOperationCopyConstructor_SerializationResultsInSame()
actual.Should().Be(expected);
}
}

[Fact]
public void OpenApiOperationCopyConstructorWithAnnotationsSucceeds()
{
var baseOperation = new OpenApiOperation
{
Annotations = new Dictionary<string, object>
{
["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"]);
}
}
}
24 changes: 23 additions & 1 deletion test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public class OpenApiSchemaTests
ExternalDocs = new()
{
Url = new("http://example.com/externalDocs")
}
},
Annotations = new Dictionary<string, object> { { "key1", "value1" }, { "key2", 2 } }
};

public static readonly OpenApiSchema AdvancedSchemaObject = new()
Expand Down Expand Up @@ -483,6 +484,27 @@ public void OpenApiSchemaCopyConstructorSucceeds()
Assert.True(actualSchema.Nullable);
}

[Fact]
public void OpenApiSchemaCopyConstructorWithAnnotationsSucceeds()
{
var baseSchema = new OpenApiSchema
{
Annotations = new Dictionary<string, object>
{
["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<IOpenApiAny> SchemaExamples()
{
return new()
Expand Down
13 changes: 10 additions & 3 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ namespace Microsoft.OpenApi.Interfaces
{
T GetEffective(Microsoft.OpenApi.Models.OpenApiDocument document);
}
public interface IOpenApiAnnotatable
{
System.Collections.Generic.IDictionary<string, object> Annotations { get; set; }
}
public interface IOpenApiElement { }
public interface IOpenApiExtensible : Microsoft.OpenApi.Interfaces.IOpenApiElement
{
Expand Down Expand Up @@ -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<string, object> Annotations { get; set; }
public Microsoft.OpenApi.Models.OpenApiComponents Components { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Interfaces.IOpenApiExtension> Extensions { get; set; }
public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; }
Expand Down Expand Up @@ -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<string, object> Annotations { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Models.OpenApiCallback> Callbacks { get; set; }
public bool Deprecated { get; set; }
public string Description { get; set; }
Expand Down Expand Up @@ -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.Models.OpenApiSchema>, 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.Models.OpenApiSchema>, 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<Microsoft.OpenApi.Models.OpenApiSchema> AllOf { get; set; }
public System.Collections.Generic.IDictionary<string, object> Annotations { get; set; }
public System.Collections.Generic.IList<Microsoft.OpenApi.Models.OpenApiSchema> AnyOf { get; set; }
public Microsoft.OpenApi.Any.IOpenApiAny Default { get; set; }
public bool Deprecated { get; set; }
Expand Down
Loading