Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -295,6 +295,11 @@ public static class OpenApiConstants
/// </summary>
public const string Schema = "schema";

/// <summary>
/// Field: ItemSchema
/// </summary>
public const string ItemSchema = "itemSchema";

/// <summary>
/// Field: Schemas
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiMediaType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public IOpenApiSchema? Schema { get; set; }

/// <summary>
/// The schema defining the type used for the items in an array media type.
/// This property is only applicable for OAS 3.2.0 and later.
/// </summary>
public IOpenApiSchema? ItemSchema { get; set; }

/// <summary>
/// Example of the media type.
/// The example object SHOULD be in the correct format as specified by the media type.
Expand Down Expand Up @@ -54,6 +60,7 @@ public OpenApiMediaType() { }
public OpenApiMediaType(OpenApiMediaType? mediaType)
{
Schema = mediaType?.Schema?.CreateShallowCopy();
ItemSchema = mediaType?.ItemSchema?.CreateShallowCopy();
Example = mediaType?.Example != null ? JsonNodeCloneHelper.Clone(mediaType.Example) : null;
Examples = mediaType?.Examples != null ? new Dictionary<string, IOpenApiExample>(mediaType.Examples) : null;
Encoding = mediaType?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(mediaType.Encoding) : null;
Expand Down Expand Up @@ -96,6 +103,18 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
// schema
writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback);

// itemSchema - only for v3.2+
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteOptionalObject(OpenApiConstants.ItemSchema, ItemSchema, callback);
}
else if (version < OpenApiSpecVersion.OpenApi3_2 && ItemSchema != null)
{
// For v3.1 and earlier, serialize as x-oai-itemSchema extension
writer.WritePropertyName(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.ItemSchema);
callback(writer, ItemSchema);
}

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

Expand Down
13 changes: 12 additions & 1 deletion src/Microsoft.OpenApi/Reader/V3/OpenApiMediaTypeDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,18 @@ internal static partial class OpenApiV3Deserializer
private static readonly PatternFieldMap<OpenApiMediaType> _mediaTypePatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, t) =>
{
// Handle x-oai-itemSchema as ItemSchema property for forward compatibility
if (p.Equals("x-oai-itemSchema", StringComparison.OrdinalIgnoreCase))
{
o.ItemSchema = LoadSchema(n, t);
}
else
{
o.AddExtension(p, LoadExtension(p, n));
}
}}
};

private static readonly AnyFieldMap<OpenApiMediaType> _mediaTypeAnyFields = new()
Expand Down
13 changes: 12 additions & 1 deletion src/Microsoft.OpenApi/Reader/V31/OpenApiMediaTypeDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,18 @@ internal static partial class OpenApiV31Deserializer
private static readonly PatternFieldMap<OpenApiMediaType> _mediaTypePatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, t) =>
{
// Handle x-oai-itemSchema as ItemSchema property for forward compatibility
if (p.Equals("x-oai-itemSchema", StringComparison.OrdinalIgnoreCase))
{
o.ItemSchema = LoadSchema(n, t);
}
else
{
o.AddExtension(p, LoadExtension(p, n));
}
}}
};

private static readonly AnyFieldMap<OpenApiMediaType> _mediaTypeAnyFields = new AnyFieldMap<OpenApiMediaType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ internal static partial class OpenApiV32Deserializer
o.Schema = LoadSchema(n, t);
}
},
{
OpenApiConstants.ItemSchema, (o, n, t) =>
{
o.ItemSchema = LoadSchema(n, t);
}
},
{
OpenApiConstants.Examples, (o, n, t) =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Tests;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V31Tests
{
[Collection("DefaultSettings")]
public class OpenApiMediaTypeTests
{
private const string SampleFolderPath = "V31Tests/Samples/OpenApiMediaType/";

[Fact]
public async Task ParseMediaTypeWithXOaiItemSchemaShouldSucceed()
{
// Act
var mediaType = await OpenApiModelFactory.LoadAsync<OpenApiMediaType>(
Path.Combine(SampleFolderPath, "mediaTypeWithXOaiItemSchema.yaml"),
OpenApiSpecVersion.OpenApi3_1,
new(),
SettingsFixture.ReaderSettings);

// Assert
mediaType.Should().BeEquivalentTo(
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = JsonSchemaType.Array
},
ItemSchema = new OpenApiSchema
{
Type = JsonSchemaType.String,
MaxLength = 100
}
},
options => options.IgnoringCyclicReferences());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
schema:
type: array
x-oai-itemSchema:
type: string
maxLength: 100
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Tests;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V32Tests
{
[Collection("DefaultSettings")]
public class OpenApiMediaTypeTests
{
private const string SampleFolderPath = "V32Tests/Samples/OpenApiMediaType/";

[Fact]
public async Task ParseMediaTypeWithItemSchemaShouldSucceed()
{
// Act
var mediaType = await OpenApiModelFactory.LoadAsync<OpenApiMediaType>(
Path.Combine(SampleFolderPath, "mediaTypeWithItemSchema.yaml"),
OpenApiSpecVersion.OpenApi3_2,
new(),
SettingsFixture.ReaderSettings);

// Assert
mediaType.Should().BeEquivalentTo(
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = JsonSchemaType.Array
},
ItemSchema = new OpenApiSchema
{
Type = JsonSchemaType.String,
MaxLength = 100
}
},
options => options.IgnoringCyclicReferences());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
schema:
type: array
itemSchema:
type: string
maxLength: 100
138 changes: 138 additions & 0 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,5 +443,143 @@ public void MediaTypeCopyConstructorWorks()
Assert.Empty(clone.Extensions);
Assert.Null(MediaTypeWithObjectExamples.Example);
}

[Fact]
public async Task SerializeMediaTypeWithItemSchemaAsV32JsonWorks()
{
// Arrange
var mediaType = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = JsonSchemaType.Array
},
ItemSchema = new OpenApiSchema
{
Type = JsonSchemaType.String,
MaxLength = 100
}
};

var expected =
"""
{
"schema": {
"type": "array"
},
"itemSchema": {
"maxLength": 100,
"type": "string"
}
}
""";

// Act
var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);

// Assert
actual = actual.MakeLineBreaksEnvironmentNeutral();
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(expected, actual);
}

[Fact]
public async Task SerializeMediaTypeWithItemSchemaAsV31JsonWorks()
{
// Arrange
var mediaType = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = JsonSchemaType.Array
},
ItemSchema = new OpenApiSchema
{
Type = JsonSchemaType.String,
MaxLength = 100
}
};

var expected =
"""
{
"schema": {
"type": "array"
},
"x-oai-itemSchema": {
"maxLength": 100,
"type": "string"
}
}
""";

// Act
var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);

// Assert
actual = actual.MakeLineBreaksEnvironmentNeutral();
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(expected, actual);
}

[Fact]
public async Task SerializeMediaTypeWithItemSchemaAsV3JsonWorks()
{
// Arrange
var mediaType = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = JsonSchemaType.Array
},
ItemSchema = new OpenApiSchema
{
Type = JsonSchemaType.String,
MaxLength = 100
}
};

var expected =
"""
{
"schema": {
"type": "array"
},
"x-oai-itemSchema": {
"maxLength": 100,
"type": "string"
}
}
""";

// Act
var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0);

// Assert
actual = actual.MakeLineBreaksEnvironmentNeutral();
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(expected, actual);
}

[Fact]
public void MediaTypeCopyConstructorCopiesItemSchema()
{
// Arrange
var original = new OpenApiMediaType
{
ItemSchema = new OpenApiSchema
{
Type = JsonSchemaType.String
}
};

// Act
var clone = new OpenApiMediaType(original);

// Assert
Assert.NotNull(clone.ItemSchema);
Assert.Equal(JsonSchemaType.String, clone.ItemSchema.Type);
Assert.NotSame(original.ItemSchema, clone.ItemSchema);
}
}
}
2 changes: 2 additions & 0 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ namespace Microsoft.OpenApi
public const string Implicit = "implicit";
public const string In = "in";
public const string Info = "info";
public const string ItemSchema = "itemSchema";
public const string Items = "items";
public const string Json = "json";
public const string JsonSchemaDialect = "jsonSchemaDialect";
Expand Down Expand Up @@ -891,6 +892,7 @@ namespace Microsoft.OpenApi
public System.Text.Json.Nodes.JsonNode? Example { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExample>? Examples { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; set; }
public Microsoft.OpenApi.IOpenApiSchema? ItemSchema { get; set; }
public Microsoft.OpenApi.IOpenApiSchema? Schema { get; set; }
public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { }
public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { }
Expand Down