diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs index 3bb10a555..371e7da80 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs @@ -56,6 +56,13 @@ public static OpenApiPathItem LoadPathItem(ParseNode node) { var mapNode = node.CheckMapNode("PathItem"); + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var refObject = mapNode.GetReferencedObject(ReferenceType.Path, pointer); + return refObject; + } + var pathItem = new OpenApiPathItem(); ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index ebe372d40..889ce4cb8 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs @@ -21,6 +21,8 @@ internal class OpenApiV3VersionService : IOpenApiVersionService { public OpenApiDiagnostic Diagnostic { get; } + private static readonly char[] _pathSeparator = new char[] { '/' }; + /// /// Create Parsing Context /// @@ -129,6 +131,20 @@ public OpenApiReference ConvertToOpenApiReference( } id = localSegments[3]; } + else if (id.StartsWith("/paths/")) + { + var localSegments = segments[1].Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries); + if (localSegments.Length == 2) + { + // The reference of a path may contain JSON escape character ~1 for the forward-slash character, replace this otherwise + // the reference cannot be resolved. + id = localSegments[1].Replace("~1", "/"); + } + else + { + throw new OpenApiException("Referenced Path mismatch"); + } + } else { openApiReference.IsFragrament = true; diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 5177e4f45..c172d84c7 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -504,6 +504,9 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool case ReferenceType.Callback: return this.Components.Callbacks[reference.Id]; + case ReferenceType.Path: + return this.Paths[reference.Id]; + default: throw new OpenApiException(Properties.SRResource.InvalidReferenceType); } diff --git a/src/Microsoft.OpenApi/Models/ReferenceType.cs b/src/Microsoft.OpenApi/Models/ReferenceType.cs index 6ac0c9ed2..9d8984c96 100644 --- a/src/Microsoft.OpenApi/Models/ReferenceType.cs +++ b/src/Microsoft.OpenApi/Models/ReferenceType.cs @@ -58,6 +58,11 @@ public enum ReferenceType /// /// Tags item. /// - [Display("tags")] Tag + [Display("tags")] Tag, + + /// + /// Paths item. + /// + [Display("paths")] Path } } diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index 3d9e978c9..1da6cc9cf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -447,7 +447,8 @@ internal void Walk(OpenApiPathItem pathItem) _visitor.Visit(pathItem); - if (pathItem != null) + // The path may be a reference + if (pathItem != null && !ProcessAsReference(pathItem)) { Walk(OpenApiConstants.Parameters, () => Walk(pathItem.Parameters)); Walk(pathItem.Operations); diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs index f7368b09b..6b94a161b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs @@ -124,5 +124,24 @@ public void ParseLocalFileReference() reference.Type.Should().Be(referenceType); reference.ExternalResource.Should().Be(input); } + + [Fact] + public void ParseExternalPathReference() + { + // Arrange + var versionService = new OpenApiV3VersionService(Diagnostic); + var externalResource = "externalSchema.json"; + var referenceJsonEscaped = "/paths/~1applications~1{AppUUID}~1services~1{ServiceName}"; + var input = $"{externalResource}#{referenceJsonEscaped}"; + var id = "/applications/{AppUUID}/services/{ServiceName}"; + + // Act + var reference = versionService.ConvertToOpenApiReference(input, null); + + // Assert + reference.Type.Should().BeNull(); + reference.ExternalResource.Should().Be(externalResource); + reference.Id.Should().Be(id); + } } }