diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs index 67a9b0495..157992b4a 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs @@ -193,7 +193,11 @@ private static (string, string) GetReferenceIdAndExternalResource(string pointer var refId = refSegments.Last(); var isExternalResource = !refSegments.First().StartsWith("#", StringComparison.OrdinalIgnoreCase); - string externalResource = isExternalResource ? $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}" : null; + string externalResource = null; + if (isExternalResource) + { + externalResource = pointer.Split('#').FirstOrDefault()?.TrimEnd('#'); + } return (refId, externalResource); } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs index 92e7770df..05856028e 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs @@ -165,7 +165,7 @@ private static (string, string) GetReferenceIdAndExternalResource(string pointer string externalResource = null; if (isExternalResource && pointer.Contains('#')) { - externalResource = $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}"; + externalResource = pointer.Split('#').FirstOrDefault()?.TrimEnd('#'); } return (refId, externalResource); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiCompoentsTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiCompoentsTests.cs new file mode 100644 index 000000000..902d7a910 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiCompoentsTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class OpenApiCompoentsTests + { + [Theory] + [InlineData("./FirstLevel/SecondLevel/ThridLevel/File.json#/components/schemas/ExternalRelativePathModel", "ExternalRelativePathModel", "./FirstLevel/SecondLevel/ThridLevel/File.json")] + [InlineData("File.json#/components/schemas/ExternalSimpleRelativePathModel", "ExternalSimpleRelativePathModel", "File.json")] + [InlineData("A:\\Dir\\File.json#/components/schemas/ExternalAbsWindowsPathModel", "ExternalAbsWindowsPathModel", "A:\\Dir\\File.json")] + [InlineData("/Dir/File.json#/components/schemas/ExternalAbsUnixPathModel", "ExternalAbsUnixPathModel", "/Dir/File.json")] + [InlineData("https://host.lan:1234/path/to/file/resource.json#/components/schemas/ExternalHttpsModel", "ExternalHttpsModel", "https://host.lan:1234/path/to/file/resource.json")] + [InlineData("File.json", "File.json", null)] + public void ParseExternalSchemaReferenceShouldSucceed(string reference, string referenceId, string externalResource) + { + var input = $@"{{ + ""schemas"": {{ + ""Model"": {{ + ""$ref"": ""{reference.Replace("\\", "\\\\")}"" + }} + }} +}} +"; + var openApiDocument = new OpenApiDocument(); + + // Act + var components = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_1, openApiDocument, out _, "json"); + + // Assert + var schema = components.Schemas["Model"] as OpenApiSchemaReference; + var expected = new OpenApiSchemaReference(referenceId, openApiDocument, externalResource); + Assert.Equivalent(expected, schema); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 2fd230a19..04d6de97d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -388,5 +388,65 @@ public async Task ParseAdvancedSchemaWithReferenceShouldSucceed() // Assert Assert.Equal(expected, actual); } + + [Fact] + public async Task ParseExternalReferenceSchemaShouldSucceed() + { + // Act + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalReferencesSchema.yaml")); + + // Assert + var components = result.Document.Components; + + Assert.Equivalent( + new OpenApiDiagnostic() + { + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 + }, result.Diagnostic); + + var expectedComponents = new OpenApiComponents + { + Schemas = + { + ["RelativePathModel"] = new OpenApiSchema() + { + AllOf = + { + new OpenApiSchemaReference("ExternalRelativePathModel", result.Document, "./FirstLevel/SecondLevel/ThridLevel/File.json") + } + }, + ["SimpleRelativePathModel"] = new OpenApiSchema() + { + AllOf = + { + new OpenApiSchemaReference("ExternalSimpleRelativePathModel", result.Document, "File.json") + } + }, + ["AbsoluteWindowsPathModel"] = new OpenApiSchema() + { + AllOf = + { + new OpenApiSchemaReference("ExternalAbsWindowsPathModel", result.Document, @"A:\Dir\File.json") + } + }, + ["AbsoluteUnixPathModel"] = new OpenApiSchema() + { + AllOf = + { + new OpenApiSchemaReference("ExternalAbsUnixPathModel", result.Document, "/Dir/File.json") + } + }, + ["HttpsUrlModel"] = new OpenApiSchema() + { + AllOf = + { + new OpenApiSchemaReference("ExternalHttpsModel", result.Document, "https://host.lan:1234/path/to/file/resource.json") + } + } + } + }; + + Assert.Equivalent(expectedComponents, components); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/externalReferencesSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/externalReferencesSchema.yaml new file mode 100644 index 000000000..dd276e667 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/externalReferencesSchema.yaml @@ -0,0 +1,23 @@ +# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject +openapi: 3.0.0 +info: + title: Simple Document + version: 0.9.1 +paths: { } +components: + schemas: + RelativePathModel: + allOf: + - $ref: './FirstLevel/SecondLevel/ThridLevel/File.json#/components/schemas/ExternalRelativePathModel' + SimpleRelativePathModel: + allOf: + - $ref: 'File.json#/components/schemas/ExternalSimpleRelativePathModel' + AbsoluteWindowsPathModel: + allOf: + - $ref: 'A:\Dir\File.json#/components/schemas/ExternalAbsWindowsPathModel' + AbsoluteUnixPathModel: + allOf: + - $ref: '/Dir/File.json#/components/schemas/ExternalAbsUnixPathModel' + HttpsUrlModel: + allOf: + - $ref: 'https://host.lan:1234/path/to/file/resource.json#/components/schemas/ExternalHttpsModel' \ No newline at end of file