Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
161605f
Initial plan
Copilot Oct 2, 2025
31b3821
Add support for media types components in OAS 3.2.0
Copilot Oct 2, 2025
41a231e
Add comprehensive tests for media types components support
Copilot Oct 2, 2025
c1a7106
Merge branch 'feat/oai-3-2-support' into copilot/fix-1e80ce48-c500-40…
baywet Oct 2, 2025
9928a7b
chore: partial fix of the walker implementation for media type support
baywet Oct 2, 2025
3461332
Address code review feedback: use inheritdoc and StringComparer.Ordinal
Copilot Oct 2, 2025
02cf2b8
chore: use json node deep equals for comparison
baywet Oct 3, 2025
834e41f
feat: make response request body, header and parameter content refere…
baywet Oct 3, 2025
fda6621
Add media type reference tests for various usage scenarios
Copilot Oct 3, 2025
1d80375
chore: fixes is component information while walking media types
baywet Oct 3, 2025
c574203
fix: content property for header is not getting deserialized v3/3.1/3.2
baywet Oct 3, 2025
c4238b6
feat: implements media types references resolution
baywet Oct 3, 2025
c7d7753
chore: do not serialize media type components in version prior to 3.2
baywet Oct 3, 2025
a10eec7
chore: updates comment
baywet Oct 3, 2025
de83a99
Merge branch 'feat/oai-3-2-support' into copilot/fix-1e80ce48-c500-40…
baywet Oct 3, 2025
86f4e83
chore: avoid serializing media types components for anything bellow v…
baywet Oct 3, 2025
7ebed6d
chore: refreshes benchmarks
baywet Oct 3, 2025
91f51bb
test: adds unit tests for header content property deserialization
baywet Oct 3, 2025
1fc5629
Merge branch 'feat/oai-3-2-support' into copilot/fix-1e80ce48-c500-40…
baywet Oct 3, 2025
78458e8
test: adds serialization tests for 32 media type reference serialization
baywet Oct 3, 2025
6c5f86f
test: fixes media type inlining behaviour based on settings
baywet Oct 3, 2025
49f3921
feat: implements inlining of referenced media types when serializing …
baywet Oct 3, 2025
13e2ee9
chore: updates public api export
baywet Oct 3, 2025
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 src/Microsoft.OpenApi/Models/Interfaces/IOpenApiHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ public interface IOpenApiHeader : IOpenApiDescribedElement, IOpenApiReadOnlyExte
/// <summary>
/// A map containing the representations for the header.
/// </summary>
public IDictionary<string, OpenApiMediaType>? Content { get; }
public IDictionary<string, IOpenApiMediaType>? Content { get; }

}
57 changes: 57 additions & 0 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiMediaType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;
using System.Text.Json.Nodes;

namespace Microsoft.OpenApi;

/// <summary>
/// Defines the base properties for the media type object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiMediaType : IOpenApiReadOnlyExtensible, IShallowCopyable<IOpenApiMediaType>, IOpenApiReferenceable
{
/// <summary>
/// The schema defining the type used for the request body.
/// </summary>
public IOpenApiSchema? Schema { get; }

/// <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; }

/// <summary>
/// Example of the media type.
/// The example object SHOULD be in the correct format as specified by the media type.
/// </summary>
public JsonNode? Example { get; }

/// <summary>
/// Examples of the media type.
/// Each example object SHOULD match the media type and specified schema if present.
/// </summary>
public IDictionary<string, IOpenApiExample>? Examples { get; }

/// <summary>
/// A map between a property name and its encoding information.
/// The key, being the property name, MUST exist in the schema as a property.
/// The encoding object SHALL only apply to requestBody objects
/// when the media type is multipart or application/x-www-form-urlencoded.
/// </summary>
public IDictionary<string, OpenApiEncoding>? Encoding { get; }

/// <summary>
/// An encoding object for items in an array schema.
/// Only applies when the schema is of type array.
/// </summary>
public OpenApiEncoding? ItemEncoding { get; }

/// <summary>
/// An array of encoding objects for prefixItems in an array schema.
/// Each element corresponds to a prefixItem in the schema.
/// </summary>
public IList<OpenApiEncoding>? PrefixEncoding { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,5 @@ public interface IOpenApiParameter : IOpenApiDescribedElement, IOpenApiReadOnlyE
/// When example or examples are provided in conjunction with the schema object,
/// the example MUST follow the prescribed serialization strategy for the parameter.
/// </summary>
public IDictionary<string, OpenApiMediaType>? Content { get; }
public IDictionary<string, IOpenApiMediaType>? Content { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface IOpenApiRequestBody : IOpenApiDescribedElement, IOpenApiReadOnl
/// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it.
/// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
/// </summary>
public IDictionary<string, OpenApiMediaType>? Content { get; }
public IDictionary<string, IOpenApiMediaType>? Content { get; }
/// <summary>
/// Converts the request body to a body parameter in preparation for a v2 serialization.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiReadOnlyEx
/// A map containing descriptions of potential response payloads.
/// The key is a media type or media type range and the value describes it.
/// </summary>
public IDictionary<string, OpenApiMediaType>? Content { get; }
public IDictionary<string, IOpenApiMediaType>? Content { get; }

/// <summary>
/// A map of operations links that can be followed from the response.
Expand Down
29 changes: 29 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public IDictionary<string, IOpenApiPathItem>? PathItems { get; set; }

/// <summary>
/// An object to hold reusable <see cref="IOpenApiMediaType"/> Objects.
/// </summary>
public IDictionary<string, IOpenApiMediaType>? MediaTypes { get; set; }

/// <summary>
/// This object MAY be extended with Specification Extensions.
/// </summary>
Expand All @@ -87,6 +92,7 @@ public OpenApiComponents(OpenApiComponents? components)
Links = components?.Links != null ? new Dictionary<string, IOpenApiLink>(components.Links) : null;
Callbacks = components?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(components.Callbacks) : null;
PathItems = components?.PathItems != null ? new Dictionary<string, IOpenApiPathItem>(components.PathItems) : null;
MediaTypes = components?.MediaTypes != null ? new Dictionary<string, IOpenApiMediaType>(components.MediaTypes) : null;
Extensions = components?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(components.Extensions) : null;
}

Expand Down Expand Up @@ -314,6 +320,29 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
}
});

// mediaTypes - serialize as native field in v3.2+, as extension in earlier versions
if (MediaTypes != null)
{
var mediaTypesFieldName = version >= OpenApiSpecVersion.OpenApi3_2
? OpenApiConstants.MediaTypes
: OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.MediaTypes;

writer.WriteOptionalMap(
mediaTypesFieldName,
MediaTypes,
(w, key, component) =>
{
if (component is OpenApiMediaTypeReference reference)
{
action(w, reference);
}
else
{
callback(w, component);
}
});
}

// extensions
writer.WriteExtensions(Extensions, version);
writer.WriteEndObject();
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ public static class OpenApiConstants
/// </summary>
public const string Callbacks = "callbacks";

/// <summary>
/// Field: MediaTypes
/// </summary>
public const string MediaTypes = "mediaTypes";

/// <summary>
/// Field: Url
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class OpenApiHeader : IOpenApiHeader, IOpenApiExtensible
public IDictionary<string, IOpenApiExample>? Examples { get; set; }

/// <inheritdoc/>
public IDictionary<string, OpenApiMediaType>? Content { get; set; }
public IDictionary<string, IOpenApiMediaType>? Content { get; set; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }
Expand All @@ -70,7 +70,7 @@ internal OpenApiHeader(IOpenApiHeader header)
Schema = header.Schema?.CreateShallowCopy();
Example = header.Example != null ? JsonNodeCloneHelper.Clone(header.Example) : null;
Examples = header.Examples != null ? new Dictionary<string, IOpenApiExample>(header.Examples) : null;
Content = header.Content != null ? new Dictionary<string, OpenApiMediaType>(header.Content) : null;
Content = header.Content != null ? new Dictionary<string, IOpenApiMediaType>(header.Content) : null;
Extensions = header.Extensions != null ? new Dictionary<string, IOpenApiExtension>(header.Extensions) : null;
}

Expand Down
65 changes: 32 additions & 33 deletions src/Microsoft.OpenApi/Models/OpenApiMediaType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,27 @@ namespace Microsoft.OpenApi
/// <summary>
/// Media Type Object.
/// </summary>
public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible
public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible, IOpenApiMediaType
{
/// <summary>
/// The schema defining the type used for the request body.
/// </summary>
/// <inheritdoc />
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>
/// <inheritdoc />
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.
/// </summary>
/// <inheritdoc />
public JsonNode? Example { get; set; }

/// <summary>
/// Examples of the media type.
/// Each example object SHOULD match the media type and specified schema if present.
/// </summary>
/// <inheritdoc />
public IDictionary<string, IOpenApiExample>? Examples { get; set; }

/// <summary>
/// A map between a property name and its encoding information.
/// The key, being the property name, MUST exist in the schema as a property.
/// The encoding object SHALL only apply to requestBody objects
/// when the media type is multipart or application/x-www-form-urlencoded.
/// </summary>
/// <inheritdoc />
public IDictionary<string, OpenApiEncoding>? Encoding { get; set; }

/// <summary>
/// An encoding object for items in an array schema.
/// Only applies when the schema is of type array.
/// </summary>
/// <inheritdoc />
public OpenApiEncoding? ItemEncoding { get; set; }

/// <summary>
/// An array of encoding objects for prefixItems in an array schema.
/// Each element corresponds to a prefixItem in the schema.
/// </summary>
/// <inheritdoc />
public IList<OpenApiEncoding>? PrefixEncoding { get; set; }

/// <summary>
Expand All @@ -74,11 +52,32 @@ 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;
Examples = mediaType?.Examples != null ? new Dictionary<string, IOpenApiExample>(mediaType.Examples, StringComparer.Ordinal) : null;
Encoding = mediaType?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(mediaType.Encoding, StringComparer.Ordinal) : null;
ItemEncoding = mediaType?.ItemEncoding != null ? new OpenApiEncoding(mediaType.ItemEncoding) : null;
PrefixEncoding = mediaType?.PrefixEncoding != null ? new List<OpenApiEncoding>(mediaType.PrefixEncoding.Select(e => new OpenApiEncoding(e))) : null;
Extensions = mediaType?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(mediaType.Extensions) : null;
Extensions = mediaType?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(mediaType.Extensions, StringComparer.Ordinal) : null;
}

/// <summary>
/// Initializes a copy of an <see cref="OpenApiMediaType"/> object
/// </summary>
internal OpenApiMediaType(IOpenApiMediaType 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, StringComparer.Ordinal) : null;
Encoding = mediaType?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(mediaType.Encoding, StringComparer.Ordinal) : null;
ItemEncoding = mediaType?.ItemEncoding != null ? new OpenApiEncoding(mediaType.ItemEncoding) : null;
PrefixEncoding = mediaType?.PrefixEncoding != null ? new List<OpenApiEncoding>(mediaType.PrefixEncoding.Select(e => new OpenApiEncoding(e))) : null;
Extensions = mediaType?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(mediaType.Extensions, StringComparer.Ordinal) : null;
}

/// <inheritdoc />
public IOpenApiMediaType CreateShallowCopy()
{
return new OpenApiMediaType(this);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public bool Explode
public JsonNode? Example { get; set; }

/// <inheritdoc/>
public IDictionary<string, OpenApiMediaType>? Content { get; set; }
public IDictionary<string, IOpenApiMediaType>? Content { get; set; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }
Expand All @@ -87,7 +87,7 @@ internal OpenApiParameter(IOpenApiParameter parameter)
Schema = parameter.Schema?.CreateShallowCopy();
Examples = parameter.Examples != null ? new Dictionary<string, IOpenApiExample>(parameter.Examples) : null;
Example = parameter.Example != null ? JsonNodeCloneHelper.Clone(parameter.Example) : null;
Content = parameter.Content != null ? new Dictionary<string, OpenApiMediaType>(parameter.Content) : null;
Content = parameter.Content != null ? new Dictionary<string, IOpenApiMediaType>(parameter.Content) : null;
Extensions = parameter.Extensions != null ? new Dictionary<string, IOpenApiExtension>(parameter.Extensions) : null;
AllowEmptyValue = parameter.AllowEmptyValue;
Deprecated = parameter.Deprecated;
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class OpenApiRequestBody : IOpenApiExtensible, IOpenApiRequestBody
public bool Required { get; set; }

/// <inheritdoc />
public IDictionary<string, OpenApiMediaType>? Content { get; set; }
public IDictionary<string, IOpenApiMediaType>? Content { get; set; }

/// <inheritdoc />
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }
Expand All @@ -37,7 +37,7 @@ internal OpenApiRequestBody(IOpenApiRequestBody requestBody)
Utils.CheckArgumentNull(requestBody);
Description = requestBody.Description ?? Description;
Required = requestBody.Required;
Content = requestBody.Content != null ? new Dictionary<string, OpenApiMediaType>(requestBody.Content) : null;
Content = requestBody.Content != null ? new Dictionary<string, IOpenApiMediaType>(requestBody.Content) : null;
Extensions = requestBody.Extensions != null ? new Dictionary<string, IOpenApiExtension>(requestBody.Extensions) : null;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class OpenApiResponse : IOpenApiExtensible, IOpenApiResponse
public IDictionary<string, IOpenApiHeader>? Headers { get; set; }

/// <inheritdoc/>
public IDictionary<string, OpenApiMediaType>? Content { get; set; }
public IDictionary<string, IOpenApiMediaType>? Content { get; set; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiLink>? Links { get; set; }
Expand All @@ -44,7 +44,7 @@ internal OpenApiResponse(IOpenApiResponse response)
Summary = response.Summary ?? Summary;
Description = response.Description ?? Description;
Headers = response.Headers != null ? new Dictionary<string, IOpenApiHeader>(response.Headers) : null;
Content = response.Content != null ? new Dictionary<string, OpenApiMediaType>(response.Content) : null;
Content = response.Content != null ? new Dictionary<string, IOpenApiMediaType>(response.Content) : null;
Links = response.Links != null ? new Dictionary<string, IOpenApiLink>(response.Links) : null;
Extensions = response.Extensions != null ? new Dictionary<string, IOpenApiExtension>(response.Extensions) : null;
}
Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.OpenApi/Models/ReferenceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public enum ReferenceType
/// <summary>
/// Path item.
/// </summary>
[Display("pathItems")] PathItem
[Display("pathItems")] PathItem,

/// <summary>
/// MediaTypes item.
/// </summary>
[Display("mediaTypes")] MediaType
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public string? Description
public IDictionary<string, IOpenApiExample>? Examples { get => Target?.Examples; }

/// <inheritdoc/>
public IDictionary<string, OpenApiMediaType>? Content { get => Target?.Content; }
public IDictionary<string, IOpenApiMediaType>? Content { get => Target?.Content; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension>? Extensions { get => Target?.Extensions; }
Expand Down
Loading
Loading