Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
59 changes: 59 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiEncoding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.OpenApi
{
Expand All @@ -27,6 +28,22 @@ public class OpenApiEncoding : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public IDictionary<string, IOpenApiHeader>? Headers { get; set; }

/// <summary>
/// A map of property names to their encoding information.
/// The key is the property name and the value is the encoding object.
/// </summary>
public IDictionary<string, OpenApiEncoding>? Encoding { get; set; }

/// <summary>
/// Encoding object for array items.
/// </summary>
public OpenApiEncoding? ItemEncoding { get; set; }

/// <summary>
/// Encoding objects for tuple-style arrays.
/// </summary>
public IList<OpenApiEncoding>? PrefixEncoding { get; set; }

/// <summary>
/// Describes how a specific property value will be serialized depending on its type.
/// </summary>
Expand Down Expand Up @@ -70,6 +87,9 @@ public OpenApiEncoding(OpenApiEncoding encoding)
{
ContentType = encoding?.ContentType ?? ContentType;
Headers = encoding?.Headers != null ? new Dictionary<string, IOpenApiHeader>(encoding.Headers) : null;
Encoding = encoding?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(encoding.Encoding, StringComparer.Ordinal) : null;
ItemEncoding = encoding?.ItemEncoding != null ? new OpenApiEncoding(encoding.ItemEncoding) : null;
PrefixEncoding = encoding?.PrefixEncoding != null ? new List<OpenApiEncoding>(encoding.PrefixEncoding) : null;
Style = encoding?.Style ?? Style;
Explode = encoding?._explode;
AllowReserved = encoding?.AllowReserved ?? AllowReserved;
Expand Down Expand Up @@ -119,6 +139,45 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
// headers
writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, callback);

// encoding - serialize as native field in v3.2+, as extension in earlier versions
if (Encoding != null)
{
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteOptionalMap(OpenApiConstants.Encoding, Encoding, callback);
}
else
{
writer.WriteOptionalMap(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.Encoding, Encoding, callback);
}
}

// itemEncoding - serialize as native field in v3.2+, as extension in earlier versions
if (ItemEncoding != null)
{
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteOptionalObject(OpenApiConstants.ItemEncoding, ItemEncoding, callback);
}
else
{
writer.WriteOptionalObject(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.ItemEncoding, ItemEncoding, callback);
}
}

// prefixEncoding - serialize as native field in v3.2+, as extension in earlier versions
if (PrefixEncoding != null)
{
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteOptionalCollection(OpenApiConstants.PrefixEncoding, PrefixEncoding, callback);
}
else
{
writer.WriteOptionalCollection(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.PrefixEncoding, PrefixEncoding, callback);
}
}

// style
writer.WriteProperty(OpenApiConstants.Style, Style?.GetDisplayName());

Expand Down
18 changes: 18 additions & 0 deletions src/Microsoft.OpenApi/Reader/V32/OpenApiEncodingDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ internal static partial class OpenApiV32Deserializer
o.Headers = n.CreateMap(LoadHeader, t);
}
},
{
"encoding", (o, n, t) =>
{
o.Encoding = n.CreateMap(LoadEncoding, t);
}
},
{
"itemEncoding", (o, n, t) =>
{
o.ItemEncoding = LoadEncoding(n, t);
}
},
{
"prefixEncoding", (o, n, t) =>
{
o.PrefixEncoding = n.CreateList(LoadEncoding, t);
}
},
{
"style", (o, n, _) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.OpenApi.Reader;
using System.Collections.Generic;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V32Tests
Expand All @@ -29,5 +30,46 @@ public async Task ParseEncodingWithAllowReservedShouldSucceed()
AllowReserved = true
}, encoding);
}

[Fact]
public async Task ParseEncodingWithNestedEncodingShouldSucceed()
{
// Act
var encoding = await OpenApiModelFactory.LoadAsync<OpenApiEncoding>(Path.Combine(SampleFolderPath, "encodingWithNestedEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings);

// Assert
Assert.NotNull(encoding);
Assert.Equal("application/json", encoding.ContentType);
Assert.NotNull(encoding.Headers);
Assert.Single(encoding.Headers);
Assert.NotNull(encoding.Encoding);
Assert.Equal(2, encoding.Encoding.Count);
Assert.True(encoding.Encoding.ContainsKey("nestedField"));
Assert.Equal("application/xml", encoding.Encoding["nestedField"].ContentType);
Assert.Equal(ParameterStyle.Form, encoding.Encoding["nestedField"].Style);
Assert.True(encoding.Encoding["nestedField"].Explode);
Assert.True(encoding.Encoding.ContainsKey("anotherField"));
Assert.Equal("text/plain", encoding.Encoding["anotherField"].ContentType);
}

[Fact]
public async Task ParseEncodingWithItemAndPrefixEncodingShouldSucceed()
{
// Act
var encoding = await OpenApiModelFactory.LoadAsync<OpenApiEncoding>(Path.Combine(SampleFolderPath, "encodingWithItemAndPrefixEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings);

// Assert
Assert.NotNull(encoding);
Assert.Equal("application/json", encoding.ContentType);
Assert.NotNull(encoding.ItemEncoding);
Assert.Equal("application/xml", encoding.ItemEncoding.ContentType);
Assert.Equal(ParameterStyle.Form, encoding.ItemEncoding.Style);
Assert.True(encoding.ItemEncoding.Explode);
Assert.NotNull(encoding.PrefixEncoding);
Assert.Equal(2, encoding.PrefixEncoding.Count);
Assert.Equal("text/plain", encoding.PrefixEncoding[0].ContentType);
Assert.Equal(ParameterStyle.Simple, encoding.PrefixEncoding[0].Style);
Assert.Equal("application/octet-stream", encoding.PrefixEncoding[1].ContentType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#encodingObject
contentType: application/json
itemEncoding:
contentType: application/xml
style: form
explode: true
prefixEncoding:
- contentType: text/plain
style: simple
- contentType: application/octet-stream
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#encodingObject
contentType: application/json
headers:
X-Rate-Limit-Limit:
description: The number of allowed requests in the current period
schema:
type: integer
encoding:
nestedField:
contentType: application/xml
style: form
explode: true
anotherField:
contentType: text/plain
194 changes: 194 additions & 0 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -126,5 +128,197 @@ public async Task WhenExplodeIsSetOutputShouldHaveExplode(bool? expectedExplode,
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(actual, expected);
}

[Fact]
public async Task SerializeEncodingWithNestedEncodingAsV32JsonWorks()
{
// Arrange
var encoding = new OpenApiEncoding
{
ContentType = "application/json",
Encoding = new Dictionary<string, OpenApiEncoding>
{
["nestedField"] = new OpenApiEncoding
{
ContentType = "application/xml",
Style = ParameterStyle.Form,
Explode = true
},
["anotherField"] = new OpenApiEncoding
{
ContentType = "text/plain"
}
}
};

var expected =
"""
{
"contentType": "application/json",
"encoding": {
"nestedField": {
"contentType": "application/xml",
"style": "form",
"explode": true
},
"anotherField": {
"contentType": "text/plain"
}
}
}
""";

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

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

[Fact]
public async Task SerializeEncodingWithNestedEncodingAsV32YamlWorks()
{
// Arrange
var encoding = new OpenApiEncoding
{
ContentType = "application/json",
Encoding = new Dictionary<string, OpenApiEncoding>
{
["nestedField"] = new OpenApiEncoding
{
ContentType = "application/xml",
Style = ParameterStyle.Form,
Explode = true
},
["anotherField"] = new OpenApiEncoding
{
ContentType = "text/plain"
}
}
};

var expected =
"""
contentType: application/json
encoding:
nestedField:
contentType: application/xml
style: form
explode: true
anotherField:
contentType: text/plain
""";

// Act
var actual = await encoding.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2);

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

[Fact]
public async Task SerializeEncodingWithItemAndPrefixEncodingAsV32JsonWorks()
{
// Arrange
var encoding = new OpenApiEncoding
{
ContentType = "application/json",
ItemEncoding = new OpenApiEncoding
{
ContentType = "application/xml",
Style = ParameterStyle.Form,
Explode = true
},
PrefixEncoding = new List<OpenApiEncoding>
{
new OpenApiEncoding
{
ContentType = "text/plain",
Style = ParameterStyle.Simple
},
new OpenApiEncoding
{
ContentType = "application/octet-stream"
}
}
};

var expected =
"""
{
"contentType": "application/json",
"itemEncoding": {
"contentType": "application/xml",
"style": "form",
"explode": true
},
"prefixEncoding": [
{
"contentType": "text/plain",
"style": "simple"
},
{
"contentType": "application/octet-stream"
}
]
}
""";

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

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

[Fact]
public async Task SerializeEncodingAsV31ShouldUseExtensionsForNewFields()
{
// Arrange
var encoding = new OpenApiEncoding
{
ContentType = "application/json",
Encoding = new Dictionary<string, OpenApiEncoding>
{
["nested"] = new OpenApiEncoding { ContentType = "text/plain" }
},
ItemEncoding = new OpenApiEncoding
{
ContentType = "application/xml"
},
PrefixEncoding = new List<OpenApiEncoding>
{
new OpenApiEncoding { ContentType = "text/csv" }
}
};

var expected =
"""
{
"contentType": "application/json",
"x-oai-encoding": {
"nested": {
"contentType": "text/plain"
}
},
"x-oai-itemEncoding": {
"contentType": "application/xml"
},
"x-oai-prefixEncoding": [
{
"contentType": "text/csv"
}
]
}
""";

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

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