Skip to content

Commit 31b3821

Browse files
Copilotbaywet
andcommitted
Add support for media types components in OAS 3.2.0
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 161605f commit 31b3821

File tree

9 files changed

+264
-3
lines changed

9 files changed

+264
-3
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Text.Json.Nodes;
6+
7+
namespace Microsoft.OpenApi;
8+
9+
/// <summary>
10+
/// Defines the base properties for the media type object.
11+
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
12+
/// </summary>
13+
public interface IOpenApiMediaType : IOpenApiReadOnlyExtensible, IShallowCopyable<IOpenApiMediaType>, IOpenApiReferenceable
14+
{
15+
/// <summary>
16+
/// The schema defining the type used for the request body.
17+
/// </summary>
18+
public IOpenApiSchema? Schema { get; }
19+
20+
/// <summary>
21+
/// The schema defining the type used for the items in an array media type.
22+
/// This property is only applicable for OAS 3.2.0 and later.
23+
/// </summary>
24+
public IOpenApiSchema? ItemSchema { get; }
25+
26+
/// <summary>
27+
/// Example of the media type.
28+
/// The example object SHOULD be in the correct format as specified by the media type.
29+
/// </summary>
30+
public JsonNode? Example { get; }
31+
32+
/// <summary>
33+
/// Examples of the media type.
34+
/// Each example object SHOULD match the media type and specified schema if present.
35+
/// </summary>
36+
public IDictionary<string, IOpenApiExample>? Examples { get; }
37+
38+
/// <summary>
39+
/// A map between a property name and its encoding information.
40+
/// The key, being the property name, MUST exist in the schema as a property.
41+
/// The encoding object SHALL only apply to requestBody objects
42+
/// when the media type is multipart or application/x-www-form-urlencoded.
43+
/// </summary>
44+
public IDictionary<string, OpenApiEncoding>? Encoding { get; }
45+
46+
/// <summary>
47+
/// An encoding object for items in an array schema.
48+
/// Only applies when the schema is of type array.
49+
/// </summary>
50+
public OpenApiEncoding? ItemEncoding { get; }
51+
52+
/// <summary>
53+
/// An array of encoding objects for prefixItems in an array schema.
54+
/// Each element corresponds to a prefixItem in the schema.
55+
/// </summary>
56+
public IList<OpenApiEncoding>? PrefixEncoding { get; }
57+
}

src/Microsoft.OpenApi/Models/OpenApiComponents.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
6262
/// </summary>
6363
public IDictionary<string, IOpenApiPathItem>? PathItems { get; set; }
6464

65+
/// <summary>
66+
/// An object to hold reusable <see cref="IOpenApiMediaType"/> Objects.
67+
/// </summary>
68+
public IDictionary<string, IOpenApiMediaType>? MediaTypes { get; set; }
69+
6570
/// <summary>
6671
/// This object MAY be extended with Specification Extensions.
6772
/// </summary>
@@ -87,6 +92,7 @@ public OpenApiComponents(OpenApiComponents? components)
8792
Links = components?.Links != null ? new Dictionary<string, IOpenApiLink>(components.Links) : null;
8893
Callbacks = components?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(components.Callbacks) : null;
8994
PathItems = components?.PathItems != null ? new Dictionary<string, IOpenApiPathItem>(components.PathItems) : null;
95+
MediaTypes = components?.MediaTypes != null ? new Dictionary<string, IOpenApiMediaType>(components.MediaTypes) : null;
9096
Extensions = components?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(components.Extensions) : null;
9197
}
9298

@@ -314,6 +320,29 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
314320
}
315321
});
316322

323+
// mediaTypes - serialize as native field in v3.2+, as extension in earlier versions
324+
if (MediaTypes != null)
325+
{
326+
var mediaTypesFieldName = version >= OpenApiSpecVersion.OpenApi3_2
327+
? OpenApiConstants.MediaTypes
328+
: OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.MediaTypes;
329+
330+
writer.WriteOptionalMap(
331+
mediaTypesFieldName,
332+
MediaTypes,
333+
(w, key, component) =>
334+
{
335+
if (component is OpenApiMediaTypeReference reference)
336+
{
337+
action(w, reference);
338+
}
339+
else
340+
{
341+
callback(w, component);
342+
}
343+
});
344+
}
345+
317346
// extensions
318347
writer.WriteExtensions(Extensions, version);
319348
writer.WriteEndObject();

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,11 @@ public static class OpenApiConstants
395395
/// </summary>
396396
public const string Callbacks = "callbacks";
397397

398+
/// <summary>
399+
/// Field: MediaTypes
400+
/// </summary>
401+
public const string MediaTypes = "mediaTypes";
402+
398403
/// <summary>
399404
/// Field: Url
400405
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiMediaType.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.OpenApi
1111
/// <summary>
1212
/// Media Type Object.
1313
/// </summary>
14-
public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible
14+
public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible, IOpenApiMediaType
1515
{
1616
/// <summary>
1717
/// The schema defining the type used for the request body.
@@ -81,6 +81,29 @@ public OpenApiMediaType(OpenApiMediaType? mediaType)
8181
Extensions = mediaType?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(mediaType.Extensions) : null;
8282
}
8383

84+
/// <summary>
85+
/// Initializes a copy of an <see cref="OpenApiMediaType"/> object
86+
/// </summary>
87+
internal OpenApiMediaType(IOpenApiMediaType mediaType)
88+
{
89+
Schema = mediaType?.Schema?.CreateShallowCopy();
90+
ItemSchema = mediaType?.ItemSchema?.CreateShallowCopy();
91+
Example = mediaType?.Example != null ? JsonNodeCloneHelper.Clone(mediaType.Example) : null;
92+
Examples = mediaType?.Examples != null ? new Dictionary<string, IOpenApiExample>(mediaType.Examples) : null;
93+
Encoding = mediaType?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(mediaType.Encoding) : null;
94+
ItemEncoding = mediaType?.ItemEncoding != null ? new OpenApiEncoding(mediaType.ItemEncoding) : null;
95+
PrefixEncoding = mediaType?.PrefixEncoding != null ? new List<OpenApiEncoding>(mediaType.PrefixEncoding.Select(e => new OpenApiEncoding(e))) : null;
96+
Extensions = mediaType?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(mediaType.Extensions) : null;
97+
}
98+
99+
/// <summary>
100+
/// Creates a shallow copy of this <see cref="OpenApiMediaType"/> object
101+
/// </summary>
102+
public IOpenApiMediaType CreateShallowCopy()
103+
{
104+
return new OpenApiMediaType(this);
105+
}
106+
84107
/// <summary>
85108
/// Serialize <see cref="OpenApiMediaType"/> to Open Api v3.2.
86109
/// </summary>

src/Microsoft.OpenApi/Models/ReferenceType.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public enum ReferenceType
6161
/// <summary>
6262
/// Path item.
6363
/// </summary>
64-
[Display("pathItems")] PathItem
64+
[Display("pathItems")] PathItem,
65+
66+
/// <summary>
67+
/// MediaTypes item.
68+
/// </summary>
69+
[Display("mediaTypes")] MediaType
6570
}
6671
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Text.Json.Nodes;
6+
7+
namespace Microsoft.OpenApi
8+
{
9+
/// <summary>
10+
/// Media Type Object Reference.
11+
/// </summary>
12+
public class OpenApiMediaTypeReference : BaseOpenApiReferenceHolder<OpenApiMediaType, IOpenApiMediaType, BaseOpenApiReference>, IOpenApiMediaType
13+
{
14+
/// <summary>
15+
/// Constructor initializing the reference object.
16+
/// </summary>
17+
/// <param name="referenceId">The reference Id.</param>
18+
/// <param name="hostDocument">The host OpenAPI document.</param>
19+
/// <param name="externalResource">Optional: External resource in the reference.
20+
/// It may be:
21+
/// 1. a absolute/relative file path, for example: ../commons/pet.json
22+
/// 2. a Url, for example: http://localhost/pet.json
23+
/// </param>
24+
public OpenApiMediaTypeReference(string referenceId, OpenApiDocument? hostDocument = null, string? externalResource = null) : base(referenceId, hostDocument, ReferenceType.MediaType, externalResource)
25+
{
26+
}
27+
28+
/// <summary>
29+
/// Copy constructor
30+
/// </summary>
31+
/// <param name="mediaTypeReference">The media type reference to copy</param>
32+
private OpenApiMediaTypeReference(OpenApiMediaTypeReference mediaTypeReference) : base(mediaTypeReference)
33+
{
34+
}
35+
36+
/// <inheritdoc/>
37+
public IOpenApiSchema? Schema { get => Target?.Schema; }
38+
39+
/// <inheritdoc/>
40+
public IOpenApiSchema? ItemSchema { get => Target?.ItemSchema; }
41+
42+
/// <inheritdoc/>
43+
public JsonNode? Example { get => Target?.Example; }
44+
45+
/// <inheritdoc/>
46+
public IDictionary<string, IOpenApiExample>? Examples { get => Target?.Examples; }
47+
48+
/// <inheritdoc/>
49+
public IDictionary<string, OpenApiEncoding>? Encoding { get => Target?.Encoding; }
50+
51+
/// <inheritdoc/>
52+
public OpenApiEncoding? ItemEncoding { get => Target?.ItemEncoding; }
53+
54+
/// <inheritdoc/>
55+
public IList<OpenApiEncoding>? PrefixEncoding { get => Target?.PrefixEncoding; }
56+
57+
/// <inheritdoc/>
58+
public IDictionary<string, IOpenApiExtension>? Extensions { get => Target?.Extensions; }
59+
60+
/// <inheritdoc/>
61+
public override IOpenApiMediaType CopyReferenceAsTargetElementWithOverrides(IOpenApiMediaType source)
62+
{
63+
return source is OpenApiMediaType ? new OpenApiMediaType(this) : source;
64+
}
65+
66+
/// <inheritdoc/>
67+
public IOpenApiMediaType CreateShallowCopy()
68+
{
69+
return new OpenApiMediaTypeReference(this);
70+
}
71+
72+
/// <inheritdoc/>
73+
protected override BaseOpenApiReference CopyReference(BaseOpenApiReference sourceReference)
74+
{
75+
return new BaseOpenApiReference(sourceReference);
76+
}
77+
}
78+
}

src/Microsoft.OpenApi/Services/OpenApiWalker.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ internal void Walk(OpenApiComponents? components)
161161
WalkDictionary(OpenApiConstants.Links, components.Links, static (self, item, isComponent) => self.Walk(item), isComponent);
162162
WalkDictionary(OpenApiConstants.RequestBodies, components.RequestBodies, static (self, item, isComponent) => self.Walk(item), isComponent);
163163
WalkDictionary(OpenApiConstants.Responses, components.Responses, static (self, item, isComponent) => self.Walk(item), isComponent);
164+
WalkDictionary(OpenApiConstants.MediaTypes, components.MediaTypes, static (self, item, isComponent) => self.Walk(item, isComponent), isComponent);
164165

165166
Walk(components as IOpenApiExtensible);
166167
}
@@ -831,6 +832,28 @@ internal void Walk(OpenApiMediaType mediaType)
831832
Walk(mediaType as IOpenApiExtensible);
832833
}
833834

835+
/// <summary>
836+
/// Visits <see cref="IOpenApiMediaType"/> and child objects
837+
/// </summary>
838+
internal void Walk(IOpenApiMediaType mediaType, bool isComponent = false)
839+
{
840+
if (mediaType == null)
841+
{
842+
return;
843+
}
844+
845+
if (mediaType is IOpenApiReferenceHolder openApiReferenceHolder)
846+
{
847+
Walk(openApiReferenceHolder);
848+
return;
849+
}
850+
851+
if (mediaType is OpenApiMediaType openApiMediaType)
852+
{
853+
Walk(openApiMediaType);
854+
}
855+
}
856+
834857
/// <summary>
835858
/// Visits dictionary of <see cref="OpenApiEncoding"/>
836859
/// </summary>

src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ public void RegisterComponents(OpenApiDocument document)
187187
RegisterComponent(location, item.Value);
188188
}
189189
}
190+
191+
// Register MediaTypes
192+
if (document.Components.MediaTypes != null)
193+
{
194+
foreach (var item in document.Components.MediaTypes)
195+
{
196+
location = baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + item.Key;
197+
RegisterComponent(location, item.Value);
198+
}
199+
}
190200
}
191201

192202
private static string getBaseUri(OpenApiDocument openApiDocument)
@@ -225,6 +235,7 @@ public bool RegisterComponentForDocument<T>(OpenApiDocument openApiDocument, T c
225235
IOpenApiExample => baseUri + ReferenceType.Example.GetDisplayName() + ComponentSegmentSeparator + id,
226236
IOpenApiHeader => baseUri + ReferenceType.Header.GetDisplayName() + ComponentSegmentSeparator + id,
227237
IOpenApiSecurityScheme => baseUri + ReferenceType.SecurityScheme.GetDisplayName() + ComponentSegmentSeparator + id,
238+
IOpenApiMediaType => baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + id,
228239
_ => throw new ArgumentException($"Invalid component type {componentToRegister!.GetType().Name}"),
229240
};
230241

test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ namespace Microsoft.OpenApi
150150
Microsoft.OpenApi.RuntimeExpressionAnyWrapper? RequestBody { get; }
151151
Microsoft.OpenApi.OpenApiServer? Server { get; }
152152
}
153+
public interface IOpenApiMediaType : Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiMediaType>
154+
{
155+
System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.OpenApiEncoding>? Encoding { get; }
156+
System.Text.Json.Nodes.JsonNode? Example { get; }
157+
System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExample>? Examples { get; }
158+
Microsoft.OpenApi.OpenApiEncoding? ItemEncoding { get; }
159+
Microsoft.OpenApi.IOpenApiSchema? ItemSchema { get; }
160+
System.Collections.Generic.IList<Microsoft.OpenApi.OpenApiEncoding>? PrefixEncoding { get; }
161+
Microsoft.OpenApi.IOpenApiSchema? Schema { get; }
162+
}
153163
public interface IOpenApiParameter : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiParameter>
154164
{
155165
bool AllowEmptyValue { get; }
@@ -401,6 +411,7 @@ namespace Microsoft.OpenApi
401411
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; set; }
402412
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiHeader>? Headers { get; set; }
403413
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiLink>? Links { get; set; }
414+
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiMediaType>? MediaTypes { get; set; }
404415
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiParameter>? Parameters { get; set; }
405416
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiPathItem>? PathItems { get; set; }
406417
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiRequestBody>? RequestBodies { get; set; }
@@ -501,6 +512,7 @@ namespace Microsoft.OpenApi
501512
public const string MaxLength = "maxLength";
502513
public const string MaxProperties = "maxProperties";
503514
public const string Maximum = "maximum";
515+
public const string MediaTypes = "mediaTypes";
504516
public const string MinItems = "minItems";
505517
public const string MinLength = "minLength";
506518
public const string MinProperties = "minProperties";
@@ -895,7 +907,7 @@ namespace Microsoft.OpenApi
895907
public Microsoft.OpenApi.IOpenApiLink CreateShallowCopy() { }
896908
public override void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { }
897909
}
898-
public class OpenApiMediaType : Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiSerializable
910+
public class OpenApiMediaType : Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiMediaType, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiMediaType>
899911
{
900912
public OpenApiMediaType() { }
901913
public OpenApiMediaType(Microsoft.OpenApi.OpenApiMediaType? mediaType) { }
@@ -907,11 +919,27 @@ namespace Microsoft.OpenApi
907919
public Microsoft.OpenApi.IOpenApiSchema? ItemSchema { get; set; }
908920
public System.Collections.Generic.IList<Microsoft.OpenApi.OpenApiEncoding>? PrefixEncoding { get; set; }
909921
public Microsoft.OpenApi.IOpenApiSchema? Schema { get; set; }
922+
public Microsoft.OpenApi.IOpenApiMediaType CreateShallowCopy() { }
910923
public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { }
911924
public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { }
912925
public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { }
913926
public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { }
914927
}
928+
public class OpenApiMediaTypeReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder<Microsoft.OpenApi.OpenApiMediaType, Microsoft.OpenApi.IOpenApiMediaType, Microsoft.OpenApi.BaseOpenApiReference>, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiMediaType, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiMediaType>
929+
{
930+
public OpenApiMediaTypeReference(string referenceId, Microsoft.OpenApi.OpenApiDocument? hostDocument = null, string? externalResource = null) { }
931+
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.OpenApiEncoding>? Encoding { get; }
932+
public System.Text.Json.Nodes.JsonNode? Example { get; }
933+
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExample>? Examples { get; }
934+
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; }
935+
public Microsoft.OpenApi.OpenApiEncoding? ItemEncoding { get; }
936+
public Microsoft.OpenApi.IOpenApiSchema? ItemSchema { get; }
937+
public System.Collections.Generic.IList<Microsoft.OpenApi.OpenApiEncoding>? PrefixEncoding { get; }
938+
public Microsoft.OpenApi.IOpenApiSchema? Schema { get; }
939+
protected override Microsoft.OpenApi.BaseOpenApiReference CopyReference(Microsoft.OpenApi.BaseOpenApiReference sourceReference) { }
940+
public override Microsoft.OpenApi.IOpenApiMediaType CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.IOpenApiMediaType source) { }
941+
public Microsoft.OpenApi.IOpenApiMediaType CreateShallowCopy() { }
942+
}
915943
public static class OpenApiNonDefaultRules
916944
{
917945
public static Microsoft.OpenApi.ValidationRule<Microsoft.OpenApi.IOpenApiHeader> HeaderMismatchedDataType { get; }
@@ -1800,6 +1828,8 @@ namespace Microsoft.OpenApi
18001828
Tag = 9,
18011829
[Microsoft.OpenApi.Display("pathItems")]
18021830
PathItem = 10,
1831+
[Microsoft.OpenApi.Display("mediaTypes")]
1832+
MediaType = 11,
18031833
}
18041834
public sealed class RequestExpression : Microsoft.OpenApi.RuntimeExpression
18051835
{

0 commit comments

Comments
 (0)