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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.3.2"
".": "2.3.3"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [2.3.3](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.2...v2.3.3) (2025-10-02)


### Bug Fixes

* typo in allowReserved property name for deserialization ([1633453](https://github.com/microsoft/OpenAPI.NET/commit/16334536dcb5182f26c0d58463bd15a124dd1505))
* typo in allowReserved property name for deserialization ([f7e34be](https://github.com/microsoft/OpenAPI.NET/commit/f7e34be28566a4f714d43667f8c43be7159d27a2))

## [2.3.2](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.1...v2.3.2) (2025-09-19)


Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageProjectUrl>https://github.com/Microsoft/OpenAPI.NET</PackageProjectUrl>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageTags>OpenAPI .NET</PackageTags>
<Version>2.3.2</Version>
<Version>2.3.3</Version>
</PropertyGroup>
<!-- https://github.com/clairernovotny/DeterministicBuilds#deterministic-builds -->
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ internal static partial class OpenApiV3Deserializer
"schema",
(o, n, t) => o.Schema = LoadSchema(n, t)
},
{
"content", (o, n, t) =>
{
o.Content = n.CreateMap(LoadMediaType, t);
}
},
{
"examples",
(o, n, t) => o.Examples = n.CreateMap(LoadExample, t)
Expand Down
106 changes: 52 additions & 54 deletions src/Microsoft.OpenApi/Reader/V31/OpenApiEncodingDeserializer.cs
Original file line number Diff line number Diff line change
@@ -1,76 +1,74 @@
using System;

namespace Microsoft.OpenApi.Reader.V31
namespace Microsoft.OpenApi.Reader.V31;
/// <summary>
/// Class containing logic to deserialize Open API V31 document into
/// runtime Open API object model.
/// </summary>
internal static partial class OpenApiV31Deserializer
{
/// <summary>
/// Class containing logic to deserialize Open API V31 document into
/// runtime Open API object model.
/// </summary>
internal static partial class OpenApiV31Deserializer
private static readonly FixedFieldMap<OpenApiEncoding> _encodingFixedFields = new()
{
private static readonly FixedFieldMap<OpenApiEncoding> _encodingFixedFields = new()
{
"contentType", (o, n, _) =>
{
"contentType", (o, n, _) =>
{
o.ContentType = n.GetScalarValue();
}
},
o.ContentType = n.GetScalarValue();
}
},
{
"headers", (o, n, t) =>
{
"headers", (o, n, t) =>
{
o.Headers = n.CreateMap(LoadHeader, t);
}
},
o.Headers = n.CreateMap(LoadHeader, t);
}
},
{
"style", (o, n, _) =>
{
"style", (o, n, _) =>
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
{
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
{
return;
}
o.Style = style;
return;
}
},
o.Style = style;
}
},
{
"explode", (o, n, _) =>
{
"explode", (o, n, _) =>
var explode = n.GetScalarValue();
if (explode is not null)
{
var explode = n.GetScalarValue();
if (explode is not null)
{
o.Explode = bool.Parse(explode);
}
}
},
o.Explode = bool.Parse(explode);
}
}
},
{
"allowReserved", (o, n, _) =>
{
"allowReserved", (o, n, _) =>
var allowReserved = n.GetScalarValue();
if (allowReserved is not null)
{
var allowReserved = n.GetScalarValue();
if (allowReserved is not null)
{
o.AllowReserved = bool.Parse(allowReserved);
}
o.AllowReserved = bool.Parse(allowReserved);
}
},
};

private static readonly PatternFieldMap<OpenApiEncoding> _encodingPatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
};
}
},
};

public static OpenApiEncoding LoadEncoding(ParseNode node, OpenApiDocument hostDocument)
private static readonly PatternFieldMap<OpenApiEncoding> _encodingPatternFields =
new()
{
var mapNode = node.CheckMapNode("encoding");
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
};

var encoding = new OpenApiEncoding();
foreach (var property in mapNode)
{
property.ParseField(encoding, _encodingFixedFields, _encodingPatternFields, hostDocument);
}
public static OpenApiEncoding LoadEncoding(ParseNode node, OpenApiDocument hostDocument)
{
var mapNode = node.CheckMapNode("encoding");

return encoding;
var encoding = new OpenApiEncoding();
foreach (var property in mapNode)
{
property.ParseField(encoding, _encodingFixedFields, _encodingPatternFields, hostDocument);
}

return encoding;
}
}
6 changes: 6 additions & 0 deletions src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ internal static partial class OpenApiV31Deserializer
o.Schema = LoadSchema(n, t);
}
},
{
"content", (o, n, t) =>
{
o.Content = n.CreateMap(LoadMediaType, t);
}
},
{
"examples", (o, n, t) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="6.0.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" PrivateAssets="all" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="all" />
<PackageReference Include="coverlet.msbuild" Version="6.0.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="FluentAssertions" Version="7.2.0" />
<PackageReference Include="system.text.json" Version="9.0.9" />
<PackageReference Include="xunit" Version="2.9.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

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

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

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

// Assert
Assert.Equivalent(
new OpenApiEncoding
{
ContentType = "application/x-www-form-urlencoded",
Style = ParameterStyle.Form,
Explode = true,
AllowReserved = true
}, encoding);
}
}
}
136 changes: 136 additions & 0 deletions test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiHeaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

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

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

[Fact]
public async Task ParseBasicHeaderShouldSucceed()
{
// Arrange
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicHeader.yaml"));

// Act
var header = await OpenApiModelFactory.LoadAsync<OpenApiHeader>(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings);

// Assert
Assert.Equivalent(
new OpenApiHeader
{
Description = "The number of allowed requests in the current period",
Schema = new OpenApiSchema()
{
Type = JsonSchemaType.Integer
}
}, header);
}

[Fact]
public async Task ParseHeaderWithContentShouldSucceed()
{
// Arrange
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithContent.yaml"));

// Act
var header = await OpenApiModelFactory.LoadAsync<OpenApiHeader>(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings);

// Assert
Assert.Equivalent(
new OpenApiHeader
{
Description = "A complex header with content",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new()
{
Schema = new OpenApiSchema()
{
Type = JsonSchemaType.Object,
Properties = new Dictionary<string, IOpenApiSchema>()
{
["timestamp"] = new OpenApiSchema()
{
Type = JsonSchemaType.String,
Format = "date-time"
},
["value"] = new OpenApiSchema()
{
Type = JsonSchemaType.Integer
}
}
}
}
}
}, header);
}

[Fact]
public async Task ParseHeaderWithMultipleContentTypesShouldSucceed()
{
// Arrange
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithMultipleContentTypes.yaml"));

// Act
var header = await OpenApiModelFactory.LoadAsync<OpenApiHeader>(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings);

// Assert
Assert.Equivalent(
new OpenApiHeader
{
Description = "A header that accepts multiple content types",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new()
{
Schema = new OpenApiSchema()
{
Type = JsonSchemaType.Object,
Properties = new Dictionary<string, IOpenApiSchema>()
{
["data"] = new OpenApiSchema()
{
Type = JsonSchemaType.String
}
}
}
},
["text/plain"] = new()
{
Schema = new OpenApiSchema()
{
Type = JsonSchemaType.String
}
}
}
}, header);
}

[Fact]
public async Task ParseHeaderWithStyleAndContentShouldPreferContent()
{
// Arrange
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithStyleAndContent.yaml"));

// Act
var header = await OpenApiModelFactory.LoadAsync<OpenApiHeader>(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings);

// Assert
// Both content and style can be present, content takes precedence for serialization behavior
Assert.NotNull(header.Content);
Assert.Single(header.Content);
Assert.True(header.Content.ContainsKey("application/json"));
Assert.Equal(ParameterStyle.Simple, header.Style); // Style can still be present
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#encodingObject
contentType: application/x-www-form-urlencoded
style: form
explode: true
allowReserved: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: "The number of allowed requests in the current period"
schema:
type: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
description: "A complex header with content"
content:
application/json:
schema:
type: object
properties:
timestamp:
type: string
format: date-time
value:
type: integer
Loading