Skip to content
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
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,11 @@ public static class OpenApiConstants
/// </summary>
public const string ExamplesExtension = "x-examples";

/// <summary>
/// Extension: x-jsonschema-examples
/// </summary>
public const string JsonSchemaExamplesExtension = "x-jsonschema-examples";

/// <summary>
/// Field: version3_0_0
/// </summary>
Expand Down
30 changes: 28 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@
}
}

private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,

Check failure on line 468 in src/Microsoft.OpenApi/Models/OpenApiSchema.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 28 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=microsoft_OpenAPI.NET&issues=AZ-A4PXorAfWDxf7qJIl&open=AZ-A4PXorAfWDxf7qJIl&pullRequest=2981
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
writer.WriteStartObject();
Expand Down Expand Up @@ -615,7 +615,10 @@
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback);

// example
writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e));
writer.WriteOptionalObject(
OpenApiConstants.Example,
version < OpenApiSpecVersion.OpenApi3_1 ? GetCompatibilityExample() : Example,
(w, e) => w.WriteAny(e));

// deprecated
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
Expand Down Expand Up @@ -741,6 +744,7 @@
writer.WriteOptionalObject(OpenApiConstants.IfExtension, If, callback);
writer.WriteOptionalObject(OpenApiConstants.ThenExtension, Then, callback);
writer.WriteOptionalObject(OpenApiConstants.ElseExtension, Else, callback);
writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s));
}

internal void WriteAsItemsProperties(IOpenApiWriter writer)
Expand Down Expand Up @@ -954,7 +958,7 @@
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w));

// example
writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e));
writer.WriteOptionalObject(OpenApiConstants.Example, GetCompatibilityExample(), (w, e) => w.WriteAny(e));

// x-nullable extension
SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0);
Expand Down Expand Up @@ -985,6 +989,8 @@
writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, (w, s) => s.SerializeAsV2(w));
}

writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s));

// extensions
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);

Expand Down Expand Up @@ -1019,6 +1025,26 @@
return false;
}

private JsonNode? GetCompatibilityExample()
{
return Example ?? Examples?.FirstOrDefault();
}

private IEnumerable<JsonNode>? GetCompatibilityExamplesExtension()
{
if (Examples is null || Examples.Count == 0)
{
return null;
}

if (Example is not null)
{
return Examples;
}

return Examples.Count > 1 ? Examples.Skip(1) : null;
}

private static bool IsPowerOfTwo(int x)
{
return x != 0 && (x & (x - 1)) == 0;
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OpenApi/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#nullable enable
const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string!
const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string!
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ internal static partial class OpenApiV2Deserializer
"example",
(o, n, _, _) => o.Example = n
},
{
OpenApiConstants.JsonSchemaExamplesExtension,
(o, n, _, c) => o.Examples = n.CreateListOfAny(c)
},
{
OpenApiConstants.PatternPropertiesExtension,
(o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ internal static partial class OpenApiV3Deserializer
"example",
(o, n, _, _) => o.Example = n
},
{
OpenApiConstants.JsonSchemaExamplesExtension,
(o, n, _, c) => o.Examples = n.CreateListOfAny(c)
},
{
"deprecated",
(o, n, _, _) =>
Expand Down
146 changes: 146 additions & 0 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,82 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
public async Task SerializeSingleExampleAsExampleInV2V3WhenExampleUnset(OpenApiSpecVersion version)
{
var expected = """
{
"example": "example value"
}
""";
var schema = new OpenApiSchema
{
Examples =
[
JsonValue.Create("example value")!
]
};

var actual = await schema.SerializeAsJsonAsync(version);

Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
public async Task SerializeMultipleExamplesInV2V3WhenExampleUnset(OpenApiSpecVersion version)
{
var expected = """
{
"example": "example value",
"x-jsonschema-examples": [
42
]
}
""";
var schema = new OpenApiSchema
{
Examples =
[
JsonValue.Create("example value")!,
JsonValue.Create(42)!
]
};

var actual = await schema.SerializeAsJsonAsync(version);

Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
public async Task SerializeExamplesAsJsonSchemaKeywordInV31AndLater(OpenApiSpecVersion version)
{
var expected = """
{
"examples": [
"example value",
42
]
}
""";
var schema = new OpenApiSchema
{
Examples =
[
JsonValue.Create("example value")!,
JsonValue.Create(42)!
]
};

var actual = await schema.SerializeAsJsonAsync(version);

Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
Expand Down Expand Up @@ -1837,6 +1913,76 @@ public void DeserializePatternPropertiesExtensionInV3AssignsPatternPropertiesPro
Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey("x-jsonschema-patternProperties"));
}

[Fact]
public void DeserializeExamplesExtensionInV2AssignsExamplesProperty()
{
var jsonContent = """
{
"swagger": "2.0",
"info": { "title": "Test", "version": "1.0" },
"paths": {},
"definitions": {
"TestSchema": {
"type": "string",
"example": "primary example",
"x-jsonschema-examples": [
"secondary example",
42
]
}
}
}
""";

var readResult = OpenApiDocument.Parse(jsonContent, "json");

Assert.Empty(readResult.Diagnostic.Errors);
var schema = readResult.Document.Components.Schemas["TestSchema"];
Assert.Equal("primary example", schema.Example?.GetValue<string>());
Assert.NotNull(schema.Examples);
Assert.Collection(
schema.Examples,
example => Assert.Equal("secondary example", example.GetValue<string>()),
example => Assert.Equal(42, example.GetValue<int>()));
Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension));
}

[Fact]
public void DeserializeExamplesExtensionInV3AssignsExamplesProperty()
{
var jsonContent = """
{
"openapi": "3.0.0",
"info": { "title": "Test", "version": "1.0" },
"paths": {},
"components": {
"schemas": {
"TestSchema": {
"type": "string",
"example": "primary example",
"x-jsonschema-examples": [
"secondary example",
42
]
}
}
}
}
""";

var readResult = OpenApiDocument.Parse(jsonContent, "json");

Assert.Empty(readResult.Diagnostic.Errors);
var schema = readResult.Document.Components.Schemas["TestSchema"];
Assert.Equal("primary example", schema.Example?.GetValue<string>());
Assert.NotNull(schema.Examples);
Assert.Collection(
schema.Examples,
example => Assert.Equal("secondary example", example.GetValue<string>()),
example => Assert.Equal(42, example.GetValue<int>()));
Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension));
}

[Fact]
public void DeserializeContainsExtensionsInV3AssignsContainsProperties()
{
Expand Down
Loading