diff --git a/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs b/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs index 675031fde..28ead3ff7 100644 --- a/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs @@ -337,10 +337,14 @@ private static string ResolveRelativePointer(string nodeLocation, string relativ } // Fallback on building a full path + if (nodeLocation.StartsWith("#/components/schemas/", StringComparison.OrdinalIgnoreCase)) + { // If the nodeLocation is a schema, we only want to keep the first three segments which are components/schemas/{schemaName} + return $"#/{string.Join("/", nodeLocationSegments.Take(3).Concat(relativeSegments))}"; + } #if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER - return $"#/{string.Join("/", nodeLocationSegments.SkipLast(relativeSegments.Length).Union(relativeSegments))}"; + return $"#/{string.Join("/", nodeLocationSegments.SkipLast(relativeSegments.Length).Concat(relativeSegments))}"; #else - return $"#/{string.Join("/", nodeLocationSegments.Take(nodeLocationSegments.Count - relativeSegments.Length).Union(relativeSegments))}"; + return $"#/{string.Join("/", nodeLocationSegments.Take(nodeLocationSegments.Count - relativeSegments.Length).Concat(relativeSegments))}"; #endif } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json new file mode 100644 index 000000000..37dfd220e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json @@ -0,0 +1,96 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Recursive relative reference in a subschema of an component schema", + "version": "1.0.0" + }, + "paths": { + "/items": { + "get": { + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Foo" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + }, + "parent": { + "type": [ + "object", + "null" + ], + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + }, + "parent": { + "$ref": "#/properties/parent", + "x-schema-id": "Category" + }, + "tags": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + } + }, + "required": [ + "name" + ], + "x-schema-id": "Tag" + } + } + }, + "required": [ + "name" + ], + "x-schema-id": "Category" + }, + "tags": { + "$ref": "#/properties/parent/properties/tags" + } + }, + "required": [ + "name" + ], + "x-schema-id": "Category" + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs index 455904ce8..83c89737c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs @@ -280,7 +280,7 @@ public void ResolveSubSchema_ShouldRecurseIntoAllOfComposition() Assert.Equal(JsonSchemaType.Integer, result!.Type); } [Fact] - public async Task SHouldResolveRelativeSubReference() + public async Task ShouldResolveRelativeSubReference() { // Arrange var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); @@ -296,5 +296,27 @@ public async Task SHouldResolveRelativeSubReference() Assert.Equal(JsonSchemaType.Array, seq2Property.Items.Type); Assert.Equal(JsonSchemaType.String, seq2Property.Items.Items.Type); } + [Fact] + public async Task ShouldResolveRecursiveRelativeSubReference() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "recursiveRelativeSubschemaReference.json"); + + // Act + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + + var fooComponentSchema = actual.Components.Schemas["Foo"]; + var fooSchemaParentProperty = fooComponentSchema.Properties["parent"]; + Assert.NotNull(fooSchemaParentProperty); + var fooSchemaParentPropertyTagsProperty = fooSchemaParentProperty.Properties["tags"]; + Assert.NotNull(fooSchemaParentPropertyTagsProperty); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, fooSchemaParentPropertyTagsProperty.Type); + Assert.Equal(JsonSchemaType.Object, fooSchemaParentPropertyTagsProperty.Items.Type); + + var fooSchemaTagsProperty = fooComponentSchema.Properties["tags"]; + Assert.NotNull(fooSchemaTagsProperty); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, fooSchemaTagsProperty.Type); + Assert.Equal(JsonSchemaType.Object, fooSchemaTagsProperty.Items.Type); + } } }