Skip to content

Commit 848cf88

Browse files
Copilotbaywet
andcommitted
Implement media type component deserialization for OAS 3.2
Co-authored-by: baywet <[email protected]>
1 parent fda6621 commit 848cf88

File tree

6 files changed

+40
-18
lines changed

6 files changed

+40
-18
lines changed

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -617,16 +617,14 @@ private static string ConvertByteArrayToString(byte[] hash)
617617

618618
string uriLocation;
619619
var id = reference.Id;
620-
if (!string.IsNullOrEmpty(id) && id!.Contains('/')) // this means its a URL reference
621-
{
622-
uriLocation = id!;
623-
}
624-
else
620+
var referenceV3 = !string.IsNullOrEmpty(reference.ReferenceV3) ? reference.ReferenceV3! : string.Empty;
621+
622+
// Check if we have a full reference path first
623+
if (!string.IsNullOrEmpty(referenceV3))
625624
{
626625
string relativePath;
627-
var referenceV3 = !string.IsNullOrEmpty(reference.ReferenceV3) ? reference.ReferenceV3! : string.Empty;
628-
629-
if (!string.IsNullOrEmpty(referenceV3) && IsSubComponent(referenceV3))
626+
627+
if (IsSubComponent(referenceV3))
630628
{
631629
// Enables setting the complete JSON path for nested subschemas e.g. #/components/schemas/person/properties/address
632630
if (useExternal)
@@ -641,7 +639,8 @@ private static string ConvertByteArrayToString(byte[] hash)
641639
}
642640
else
643641
{
644-
relativePath = $"#{OpenApiConstants.ComponentsSegment}{reference.Type.GetDisplayName()}/{id}";
642+
// Use the full reference path as-is (e.g., #/components/mediaTypes/application~1json)
643+
relativePath = referenceV3;
645644
}
646645

647646
Uri? externalResourceUri = useExternal ? Workspace?.GetDocumentId(reference.ExternalResource) : null;
@@ -650,6 +649,21 @@ private static string ConvertByteArrayToString(byte[] hash)
650649
? externalResourceUri.AbsoluteUri + relativePath
651650
: BaseUri + relativePath;
652651
}
652+
else if (!string.IsNullOrEmpty(id) && id!.Contains('/')) // this means its a URL reference
653+
{
654+
uriLocation = id!;
655+
}
656+
else
657+
{
658+
// Fallback: construct path from type and id
659+
string relativePath = $"#{OpenApiConstants.ComponentsSegment}{reference.Type.GetDisplayName()}/{id}";
660+
661+
Uri? externalResourceUri = useExternal ? Workspace?.GetDocumentId(reference.ExternalResource) : null;
662+
663+
uriLocation = useExternal && externalResourceUri is not null
664+
? externalResourceUri.AbsoluteUri + relativePath
665+
: BaseUri + relativePath;
666+
}
653667

654668
var absoluteUri =
655669
#if NETSTANDARD2_1 || NETCOREAPP || NET5_0_OR_GREATER

src/Microsoft.OpenApi/Reader/V32/OpenApiComponentsDeserializer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ internal static partial class OpenApiV32Deserializer
2222
{"securitySchemes", (o, n, t) => o.SecuritySchemes = n.CreateMap(LoadSecurityScheme, t)},
2323
{"links", (o, n, t) => o.Links = n.CreateMap(LoadLink, t)},
2424
{"callbacks", (o, n, t) => o.Callbacks = n.CreateMap(LoadCallback, t)},
25-
{"pathItems", (o, n, t) => o.PathItems = n.CreateMap(LoadPathItem, t)}
25+
{"pathItems", (o, n, t) => o.PathItems = n.CreateMap(LoadPathItem, t)},
26+
{"mediaTypes", (o, n, t) => o.MediaTypes = n.CreateMap(LoadMediaType, t)}
2627
};
2728

2829
private static readonly PatternFieldMap<OpenApiComponents> _componentsPatternFields =

src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ public static IOpenApiMediaType LoadMediaType(ParseNode node, OpenApiDocument ho
9090
{
9191
var mapNode = node.CheckMapNode(OpenApiConstants.Content);
9292

93+
var pointer = mapNode.GetReferencePointer();
94+
if (pointer != null)
95+
{
96+
var reference = GetReferenceIdAndExternalResource(pointer);
97+
var mediaTypeReference = new OpenApiMediaTypeReference(reference.Item1, hostDocument, reference.Item2);
98+
mediaTypeReference.Reference.SetMetadataFromMapNode(mapNode);
99+
mediaTypeReference.Reference.SetJsonPointerPath(pointer, node.Context.GetLocation());
100+
return mediaTypeReference;
101+
}
102+
93103
var mediaType = new OpenApiMediaType();
94104

95105
ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields, hostDocument);

src/Microsoft.OpenApi/Services/OpenApiWalker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ internal void Walk(IDictionary<string, IOpenApiMediaType>? content)
800800
}
801801

802802
_visitor.CurrentKeys.Content = mediaType.Key;
803-
WalkItem(mediaType.Key, mediaType.Value, static (self, item) => self.Walk(item));
803+
WalkItem(mediaType.Key, mediaType.Value, static (self, item, isComponent) => self.Walk(item, isComponent), isComponent: false);
804804
_visitor.CurrentKeys.Content = null;
805805
}
806806
}

src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,9 @@ public void RegisterComponents(OpenApiDocument document)
193193
{
194194
foreach (var item in document.Components.MediaTypes)
195195
{
196-
location = baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + item.Key;
196+
// Encode the key to match JSON Pointer encoding used in references
197+
var encodedKey = item.Key.Replace("~", "~0").Replace("/", "~1");
198+
location = baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + encodedKey;
197199
RegisterComponent(location, item.Value);
198200
}
199201
}
@@ -235,7 +237,7 @@ public bool RegisterComponentForDocument<T>(OpenApiDocument openApiDocument, T c
235237
IOpenApiExample => baseUri + ReferenceType.Example.GetDisplayName() + ComponentSegmentSeparator + id,
236238
IOpenApiHeader => baseUri + ReferenceType.Header.GetDisplayName() + ComponentSegmentSeparator + id,
237239
IOpenApiSecurityScheme => baseUri + ReferenceType.SecurityScheme.GetDisplayName() + ComponentSegmentSeparator + id,
238-
IOpenApiMediaType => baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + id,
240+
IOpenApiMediaType => baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + id.Replace("~", "~0").Replace("/", "~1"),
239241
_ => throw new ArgumentException($"Invalid component type {componentToRegister!.GetType().Name}"),
240242
};
241243

test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiMediaTypeReferenceDeserializerTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@
77

88
namespace Microsoft.OpenApi.Readers.Tests.V32Tests;
99

10-
// NOTE: These tests are currently disabled because media type reference deserialization
11-
// support needs to be implemented in the parser. The tests are kept here as a specification
12-
// of the expected behavior once parser support is added.
1310
public class OpenApiMediaTypeReferenceDeserializerTests
1411
{
15-
/*
1612
[Fact]
1713
public void ShouldDeserializeMediaTypeReferenceInRequestBodyContent()
1814
{
@@ -280,5 +276,4 @@ public void ShouldDeserializeMediaTypeReferenceInHeaderContent()
280276
Assert.NotNull(mediaTypeRef.Schema);
281277
Assert.Equal(JsonSchemaType.Array, mediaTypeRef.Schema.Type);
282278
}
283-
*/
284279
}

0 commit comments

Comments
 (0)