Skip to content

Commit 8f5bd7e

Browse files
authored
Merge pull request #2981 from microsoft/fix/examples-serialization-to-v2
fix/examples serialization to v2
2 parents d5cdce8 + dc24f54 commit 8f5bd7e

6 files changed

Lines changed: 188 additions & 2 deletions

File tree

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,11 @@ public static class OpenApiConstants
755755
/// </summary>
756756
public const string ExamplesExtension = "x-examples";
757757

758+
/// <summary>
759+
/// Extension: x-jsonschema-examples
760+
/// </summary>
761+
public const string JsonSchemaExamplesExtension = "x-jsonschema-examples";
762+
758763
/// <summary>
759764
/// Field: version3_0_0
760765
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,10 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
615615
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback);
616616

617617
// example
618-
writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e));
618+
writer.WriteOptionalObject(
619+
OpenApiConstants.Example,
620+
version < OpenApiSpecVersion.OpenApi3_1 ? GetCompatibilityExample() : Example,
621+
(w, e) => w.WriteAny(e));
619622

620623
// deprecated
621624
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
@@ -741,6 +744,7 @@ private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action<IOpenApi
741744
writer.WriteOptionalObject(OpenApiConstants.IfExtension, If, callback);
742745
writer.WriteOptionalObject(OpenApiConstants.ThenExtension, Then, callback);
743746
writer.WriteOptionalObject(OpenApiConstants.ElseExtension, Else, callback);
747+
writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s));
744748
}
745749

746750
internal void WriteAsItemsProperties(IOpenApiWriter writer)
@@ -954,7 +958,7 @@ private void SerializeAsV2(
954958
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w));
955959

956960
// example
957-
writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e));
961+
writer.WriteOptionalObject(OpenApiConstants.Example, GetCompatibilityExample(), (w, e) => w.WriteAny(e));
958962

959963
// x-nullable extension
960964
SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0);
@@ -985,6 +989,8 @@ private void SerializeAsV2(
985989
writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, (w, s) => s.SerializeAsV2(w));
986990
}
987991

992+
writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s));
993+
988994
// extensions
989995
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);
990996

@@ -1019,6 +1025,26 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion
10191025
return false;
10201026
}
10211027

1028+
private JsonNode? GetCompatibilityExample()
1029+
{
1030+
return Example ?? Examples?.FirstOrDefault();
1031+
}
1032+
1033+
private IEnumerable<JsonNode>? GetCompatibilityExamplesExtension()
1034+
{
1035+
if (Examples is null || Examples.Count == 0)
1036+
{
1037+
return null;
1038+
}
1039+
1040+
if (Example is not null)
1041+
{
1042+
return Examples;
1043+
}
1044+
1045+
return Examples.Count > 1 ? Examples.Skip(1) : null;
1046+
}
1047+
10221048
private static bool IsPowerOfTwo(int x)
10231049
{
10241050
return x != 0 && (x & (x - 1)) == 0;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#nullable enable
22
const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string!
3+
const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string!

src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ internal static partial class OpenApiV2Deserializer
267267
"example",
268268
(o, n, _, _) => o.Example = n
269269
},
270+
{
271+
OpenApiConstants.JsonSchemaExamplesExtension,
272+
(o, n, _, c) => o.Examples = n.CreateListOfAny(c)
273+
},
270274
{
271275
OpenApiConstants.PatternPropertiesExtension,
272276
(o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c)

src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ internal static partial class OpenApiV3Deserializer
280280
"example",
281281
(o, n, _, _) => o.Example = n
282282
},
283+
{
284+
OpenApiConstants.JsonSchemaExamplesExtension,
285+
(o, n, _, c) => o.Examples = n.CreateListOfAny(c)
286+
},
283287
{
284288
"deprecated",
285289
(o, n, _, _) =>

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,82 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi
17461746
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
17471747
}
17481748

1749+
[Theory]
1750+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
1751+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
1752+
public async Task SerializeSingleExampleAsExampleInV2V3WhenExampleUnset(OpenApiSpecVersion version)
1753+
{
1754+
var expected = """
1755+
{
1756+
"example": "example value"
1757+
}
1758+
""";
1759+
var schema = new OpenApiSchema
1760+
{
1761+
Examples =
1762+
[
1763+
JsonValue.Create("example value")!
1764+
]
1765+
};
1766+
1767+
var actual = await schema.SerializeAsJsonAsync(version);
1768+
1769+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1770+
}
1771+
1772+
[Theory]
1773+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
1774+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
1775+
public async Task SerializeMultipleExamplesInV2V3WhenExampleUnset(OpenApiSpecVersion version)
1776+
{
1777+
var expected = """
1778+
{
1779+
"example": "example value",
1780+
"x-jsonschema-examples": [
1781+
42
1782+
]
1783+
}
1784+
""";
1785+
var schema = new OpenApiSchema
1786+
{
1787+
Examples =
1788+
[
1789+
JsonValue.Create("example value")!,
1790+
JsonValue.Create(42)!
1791+
]
1792+
};
1793+
1794+
var actual = await schema.SerializeAsJsonAsync(version);
1795+
1796+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1797+
}
1798+
1799+
[Theory]
1800+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
1801+
public async Task SerializeExamplesAsJsonSchemaKeywordInV31AndLater(OpenApiSpecVersion version)
1802+
{
1803+
var expected = """
1804+
{
1805+
"examples": [
1806+
"example value",
1807+
42
1808+
]
1809+
}
1810+
""";
1811+
var schema = new OpenApiSchema
1812+
{
1813+
Examples =
1814+
[
1815+
JsonValue.Create("example value")!,
1816+
JsonValue.Create(42)!
1817+
]
1818+
};
1819+
1820+
var actual = await schema.SerializeAsJsonAsync(version);
1821+
1822+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1823+
}
1824+
17491825
[Theory]
17501826
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
17511827
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
@@ -1837,6 +1913,76 @@ public void DeserializePatternPropertiesExtensionInV3AssignsPatternPropertiesPro
18371913
Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey("x-jsonschema-patternProperties"));
18381914
}
18391915

1916+
[Fact]
1917+
public void DeserializeExamplesExtensionInV2AssignsExamplesProperty()
1918+
{
1919+
var jsonContent = """
1920+
{
1921+
"swagger": "2.0",
1922+
"info": { "title": "Test", "version": "1.0" },
1923+
"paths": {},
1924+
"definitions": {
1925+
"TestSchema": {
1926+
"type": "string",
1927+
"example": "primary example",
1928+
"x-jsonschema-examples": [
1929+
"secondary example",
1930+
42
1931+
]
1932+
}
1933+
}
1934+
}
1935+
""";
1936+
1937+
var readResult = OpenApiDocument.Parse(jsonContent, "json");
1938+
1939+
Assert.Empty(readResult.Diagnostic.Errors);
1940+
var schema = readResult.Document.Components.Schemas["TestSchema"];
1941+
Assert.Equal("primary example", schema.Example?.GetValue<string>());
1942+
Assert.NotNull(schema.Examples);
1943+
Assert.Collection(
1944+
schema.Examples,
1945+
example => Assert.Equal("secondary example", example.GetValue<string>()),
1946+
example => Assert.Equal(42, example.GetValue<int>()));
1947+
Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension));
1948+
}
1949+
1950+
[Fact]
1951+
public void DeserializeExamplesExtensionInV3AssignsExamplesProperty()
1952+
{
1953+
var jsonContent = """
1954+
{
1955+
"openapi": "3.0.0",
1956+
"info": { "title": "Test", "version": "1.0" },
1957+
"paths": {},
1958+
"components": {
1959+
"schemas": {
1960+
"TestSchema": {
1961+
"type": "string",
1962+
"example": "primary example",
1963+
"x-jsonschema-examples": [
1964+
"secondary example",
1965+
42
1966+
]
1967+
}
1968+
}
1969+
}
1970+
}
1971+
""";
1972+
1973+
var readResult = OpenApiDocument.Parse(jsonContent, "json");
1974+
1975+
Assert.Empty(readResult.Diagnostic.Errors);
1976+
var schema = readResult.Document.Components.Schemas["TestSchema"];
1977+
Assert.Equal("primary example", schema.Example?.GetValue<string>());
1978+
Assert.NotNull(schema.Examples);
1979+
Assert.Collection(
1980+
schema.Examples,
1981+
example => Assert.Equal("secondary example", example.GetValue<string>()),
1982+
example => Assert.Equal(42, example.GetValue<int>()));
1983+
Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension));
1984+
}
1985+
18401986
[Fact]
18411987
public void DeserializeContainsExtensionsInV3AssignsContainsProperties()
18421988
{

0 commit comments

Comments
 (0)