Skip to content

Commit b7bc6be

Browse files
authored
Merge pull request #2389 from microsoft/fix/relative-json-references
fix: a bug where relative relative and sub-component JSON references would not resolve properly
2 parents e82d43f + 3510c72 commit b7bc6be

File tree

3 files changed

+91
-7
lines changed

3 files changed

+91
-7
lines changed

src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,11 @@ protected virtual void SetAdditional31MetadataFromMapNode(JsonObject jsonObject)
300300
internal void SetJsonPointerPath(string pointer, string nodeLocation)
301301
{
302302
// Relative reference to internal JSON schema node/resource (e.g. "#/properties/b")
303-
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.Contains("/components/schemas"))
303+
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER
304+
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.Contains("/components/schemas", StringComparison.OrdinalIgnoreCase))
305+
#else
306+
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.ToLowerInvariant().Contains("/components/schemas"))
307+
#endif
304308
{
305309
ReferenceV3 = ResolveRelativePointer(nodeLocation, pointer);
306310
}
@@ -316,23 +320,28 @@ internal void SetJsonPointerPath(string pointer, string nodeLocation)
316320
private static string ResolveRelativePointer(string nodeLocation, string relativeRef)
317321
{
318322
// Convert nodeLocation to path segments
319-
var segments = nodeLocation.TrimStart('#').Split(['/'], StringSplitOptions.RemoveEmptyEntries).ToList();
323+
var nodeLocationSegments = nodeLocation.TrimStart('#').Split(['/'], StringSplitOptions.RemoveEmptyEntries).ToList();
320324

321325
// Convert relativeRef to dynamic segments
322326
var relativeSegments = relativeRef.TrimStart('#').Split(['/'], StringSplitOptions.RemoveEmptyEntries);
323327

324328
// Locate the first occurrence of relativeRef segments in the full path
325-
for (int i = 0; i <= segments.Count - relativeSegments.Length; i++)
329+
for (int i = 0; i <= nodeLocationSegments.Count - relativeSegments.Length; i++)
326330
{
327-
if (relativeSegments.SequenceEqual(segments.Skip(i).Take(relativeSegments.Length)))
331+
if (relativeSegments.SequenceEqual(nodeLocationSegments.Skip(i).Take(relativeSegments.Length), StringComparer.Ordinal) &&
332+
nodeLocationSegments.Take(i + relativeSegments.Length).ToArray() is {Length: > 0} matchingSegments)
328333
{
329334
// Trim to include just the matching segment chain
330-
segments = [.. segments.Take(i + relativeSegments.Length)];
331-
break;
335+
return $"#/{string.Join("/", matchingSegments)}";
332336
}
333337
}
334338

335-
return $"#/{string.Join("/", segments)}";
339+
// Fallback on building a full path
340+
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER
341+
return $"#/{string.Join("/", nodeLocationSegments.SkipLast(relativeSegments.Length).Union(relativeSegments))}";
342+
#else
343+
return $"#/{string.Join("/", nodeLocationSegments.Take(nodeLocationSegments.Count - relativeSegments.Length).Union(relativeSegments))}";
344+
#endif
336345
}
337346
}
338347
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "Relative reference in a subschema of an component schema",
5+
"version": "1.0.0"
6+
},
7+
"paths": {
8+
"/items": {
9+
"get": {
10+
"responses": {
11+
"200": {
12+
"description": "ok",
13+
"content": {
14+
"application/json": {
15+
"schema": {
16+
"$ref": "#/components/schemas/Foo"
17+
}
18+
}
19+
}
20+
}
21+
}
22+
}
23+
}
24+
},
25+
"components": {
26+
"schemas": {
27+
"Foo": {
28+
"type": "object",
29+
"properties": {
30+
"seq1": {
31+
"type": [
32+
"array",
33+
"null"
34+
],
35+
"items": {
36+
"type": "array",
37+
"items": {
38+
"type": "string",
39+
"format": null,
40+
"x-schema-id": null
41+
}
42+
}
43+
},
44+
"seq2": {
45+
"type": [
46+
"array",
47+
"null"
48+
],
49+
"items": {
50+
"$ref": "#/properties/seq1/items"
51+
}
52+
}
53+
},
54+
"x-schema-id": "ContainerType"
55+
}
56+
}
57+
}
58+
}

test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,5 +279,22 @@ public void ResolveSubSchema_ShouldRecurseIntoAllOfComposition()
279279
Assert.NotNull(result);
280280
Assert.Equal(JsonSchemaType.Integer, result!.Type);
281281
}
282+
[Fact]
283+
public async Task SHouldResolveRelativeSubReference()
284+
{
285+
// Arrange
286+
var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json");
287+
288+
// Act
289+
var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings);
290+
291+
var fooComponentSchema = actual.Components.Schemas["Foo"];
292+
var seq1Property = fooComponentSchema.Properties["seq1"];
293+
Assert.NotNull(seq1Property);
294+
var seq2Property = fooComponentSchema.Properties["seq2"];
295+
Assert.NotNull(seq2Property);
296+
Assert.Equal(JsonSchemaType.Array, seq2Property.Items.Type);
297+
Assert.Equal(JsonSchemaType.String, seq2Property.Items.Items.Type);
298+
}
282299
}
283300
}

0 commit comments

Comments
 (0)