diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs index cd74df4b4..5d91eb833 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs @@ -43,6 +43,10 @@ internal static partial class OpenApiV3Deserializer "explode", (o, n) => o.Explode = bool.Parse(n.GetScalarValue()) }, + { + "content", + (o, n) => o.Content = n.CreateMap(LoadMediaType) + }, { "schema", (o, n) => o.Schema = LoadSchema(n) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs new file mode 100644 index 000000000..79626ed00 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Readers.V3; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V3Tests +{ + [Collection("DefaultSettings")] + public class OpenApiHeaderTests + { + private const string SampleFolderPath = "V3Tests/Samples/OpenApiHeader/"; + + [Fact] + public void ParseBasicHeaderShouldSucceed() + { + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicHeader.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var node = new MapNode(context, (YamlMappingNode)yamlNode); + + // Act + var header = OpenApiV3Deserializer.LoadHeader(node); + + // Assert + Assert.Equivalent( + new OpenApiHeader + { + Description = "The number of allowed requests in the current period", + Schema = new OpenApiSchema() + { + Type = "integer" + } + }, header); + } + + [Fact] + public void ParseHeaderWithContentShouldSucceed() + { + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithContent.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var node = new MapNode(context, (YamlMappingNode)yamlNode); + + // Act + var header = OpenApiV3Deserializer.LoadHeader(node); + + // Assert + Assert.Equivalent( + new OpenApiHeader + { + Description = "A complex header with content", + Content = new Dictionary() + { + ["application/json"] = new() + { + Schema = new OpenApiSchema() + { + Type = "object", + Properties = new Dictionary() + { + ["timestamp"] = new OpenApiSchema() + { + Type = "string", + Format = "date-time" + }, + ["value"] = new OpenApiSchema() + { + Type = "integer" + } + } + } + } + } + }, header); + } + + [Fact] + public void ParseHeaderWithMultipleContentTypesShouldSucceed() + { + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var node = new MapNode(context, (YamlMappingNode)yamlNode); + + // Act + var header = OpenApiV3Deserializer.LoadHeader(node); + + // Assert + Assert.Equivalent( + new OpenApiHeader + { + Description = "A header that accepts multiple content types", + Content = new Dictionary() + { + ["application/json"] = new() + { + Schema = new OpenApiSchema() + { + Type = "object", + Properties = new Dictionary() + { + ["data"] = new OpenApiSchema() + { + Type = "string" + } + } + } + }, + ["text/plain"] = new() + { + Schema = new OpenApiSchema() + { + Type = "string" + } + } + } + }, header); + } + + [Fact] + public void ParseHeaderWithStyleAndContentShouldPreferContent() + { + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithStyleAndContent.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var node = new MapNode(context, (YamlMappingNode)yamlNode); + + // Act + var header = OpenApiV3Deserializer.LoadHeader(node); + + // 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 + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/basicHeader.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/basicHeader.yaml new file mode 100644 index 000000000..015804759 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/basicHeader.yaml @@ -0,0 +1,3 @@ +description: "The number of allowed requests in the current period" +schema: + type: integer \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithContent.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithContent.yaml new file mode 100644 index 000000000..dc2df9ffd --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithContent.yaml @@ -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 \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithMultipleContentTypes.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithMultipleContentTypes.yaml new file mode 100644 index 000000000..82c74ea60 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithMultipleContentTypes.yaml @@ -0,0 +1,11 @@ +description: "A header that accepts multiple content types" +content: + application/json: + schema: + type: object + properties: + data: + type: string + text/plain: + schema: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithStyleAndContent.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithStyleAndContent.yaml new file mode 100644 index 000000000..fbe176683 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiHeader/headerWithStyleAndContent.yaml @@ -0,0 +1,11 @@ +description: "A header with both style and content (content should take precedence)" +style: simple +schema: + type: string +content: + application/json: + schema: + type: object + properties: + value: + type: string \ No newline at end of file