diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
index 5fee30ac2..f04f47680 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
@@ -24,7 +24,7 @@ namespace Microsoft.OpenApi.Models
public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
{
///
- /// Related workspace containing OpenApiDocuments that are referenced in this document
+ /// Related workspace containing components that are referenced in a document
///
public OpenApiWorkspace Workspace { get; set; }
diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
index 6915d60bd..a3462da70 100644
--- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
+++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
@@ -28,7 +28,7 @@ internal async Task LoadAsync(OpenApiReference reference,
{
_workspace.AddDocumentId(reference.ExternalResource, document.BaseUri);
var version = diagnostic?.SpecificationVersion ?? OpenApiSpecVersion.OpenApi3_0;
- _workspace.RegisterComponents(document, version);
+ _workspace.RegisterComponents(document);
document.Workspace = _workspace;
// Collect remote references by walking document
diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs
index b0e2a29ae..f33d98465 100644
--- a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs
@@ -252,7 +252,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
FixRequestBodyReferences(openApiDoc);
// Register components
- openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi2_0);
+ openApiDoc.Workspace.RegisterComponents(openApiDoc);
return openApiDoc;
}
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs
index 7a17de018..3fcdb9af7 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs
@@ -54,7 +54,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc);
// Register components
- openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_0);
+ openApiDoc.Workspace.RegisterComponents(openApiDoc);
return openApiDoc;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs
index b6e0fe5fc..8137fb460 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs
@@ -53,7 +53,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc);
// Register components
- openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_1);
+ openApiDoc.Workspace.RegisterComponents(openApiDoc);
return openApiDoc;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
index a56590bf1..d6c9d0fcf 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
@@ -157,9 +157,11 @@ private static (string, string) GetReferenceIdAndExternalResource(string pointer
string refId = !pointer.Contains('#') ? pointer : refSegments.Last();
var isExternalResource = !refSegments.First().StartsWith("#");
- string externalResource = isExternalResource
- ? $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}"
- : null;
+ string externalResource = null;
+ if (isExternalResource && pointer.Contains('#'))
+ {
+ externalResource = $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}";
+ }
return (refId, externalResource);
}
diff --git a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs
deleted file mode 100644
index 226853a13..000000000
--- a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.OpenApi.Extensions;
-using Microsoft.OpenApi.Models;
-
-namespace Microsoft.OpenApi.Services
-{
- internal static class OpenApiComponentsRegistryExtensions
- {
- public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDocument document, OpenApiSpecVersion version = OpenApiSpecVersion.OpenApi3_0)
- {
- if (document?.Components == null) return;
-
- string baseUri = document.BaseUri + OpenApiConstants.ComponentsSegment;
- string location;
-
- // Register Schema
- foreach (var item in document.Components.Schemas)
- {
- if (item.Value.Id != null)
- {
- location = item.Value.Id;
- }
- else
- {
- location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key;
- }
-
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register Parameters
- foreach (var item in document.Components.Parameters)
- {
- location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register Responses
- foreach (var item in document.Components.Responses)
- {
- location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register RequestBodies
- foreach (var item in document.Components.RequestBodies)
- {
- location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register Links
- foreach (var item in document.Components.Links)
- {
- location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register Callbacks
- foreach (var item in document.Components.Callbacks)
- {
- location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register PathItems
- foreach (var item in document.Components.PathItems)
- {
- location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register Examples
- foreach (var item in document.Components.Examples)
- {
- location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register Headers
- foreach (var item in document.Components.Headers)
- {
- location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
-
- // Register SecuritySchemes
- foreach (var item in document.Components.SecuritySchemes)
- {
- location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key;
- workspace.RegisterComponent(location, item.Value);
- }
- }
- }
-}
diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
index 319a5d63f..7652ed242 100644
--- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
+++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
@@ -54,6 +55,90 @@ public int ComponentsCount()
return _IOpenApiReferenceableRegistry.Count + _artifactsRegistry.Count;
}
+ ///
+ /// Registers a document's components into the workspace
+ ///
+ ///
+ public void RegisterComponents(OpenApiDocument document)
+ {
+ if (document?.Components == null) return;
+
+ string baseUri = document.BaseUri + OpenApiConstants.ComponentsSegment;
+ string location;
+
+ // Register Schema
+ foreach (var item in document.Components.Schemas)
+ {
+ location = item.Value.Id ?? baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key;
+
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register Parameters
+ foreach (var item in document.Components.Parameters)
+ {
+ location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register Responses
+ foreach (var item in document.Components.Responses)
+ {
+ location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register RequestBodies
+ foreach (var item in document.Components.RequestBodies)
+ {
+ location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register Links
+ foreach (var item in document.Components.Links)
+ {
+ location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register Callbacks
+ foreach (var item in document.Components.Callbacks)
+ {
+ location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register PathItems
+ foreach (var item in document.Components.PathItems)
+ {
+ location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register Examples
+ foreach (var item in document.Components.Examples)
+ {
+ location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register Headers
+ foreach (var item in document.Components.Headers)
+ {
+ location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+
+ // Register SecuritySchemes
+ foreach (var item in document.Components.SecuritySchemes)
+ {
+ location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key;
+ RegisterComponent(location, item.Value);
+ }
+ }
+
+
///
/// Registers a component in the component registry.
///
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs
index 2ada8e4bd..c954387a6 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs
@@ -1,15 +1,17 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.OpenApi.Extensions;
-using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Tests;
using Microsoft.OpenApi.Writers;
+using Microsoft.OpenApi.Services;
using Xunit;
+using System.Linq;
namespace Microsoft.OpenApi.Readers.Tests.V31Tests
{
@@ -392,7 +394,7 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 });
var outputWriter = new StringWriter(CultureInfo.InvariantCulture);
- var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true } );
+ var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true });
actual.OpenApiDocument.SerializeAsV31(writer);
var serialized = outputWriter.ToString();
}
@@ -445,7 +447,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks()
}
}
};
-
+
// Serialization
var mediaType = result.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"];
@@ -461,7 +463,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks()
type: string
prop3:
type: string";
-
+
var actualMediaType = mediaType.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1);
// Assert
@@ -484,5 +486,49 @@ public void ParseDocumentWithReferenceByIdGetsResolved()
Assert.Equal("object", requestBodySchema.Type);
Assert.Equal("string", parameterSchema.Type);
}
+
+ [Fact]
+ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWorks()
+ {
+ // Arrange
+ var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath);
+
+ var settings = new OpenApiReaderSettings
+ {
+ LoadExternalRefs = true,
+ BaseUrl = new(path),
+ };
+
+ // Act
+ var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefByJsonPointer.yaml"), settings);
+ var responseSchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema;
+
+ // Assert
+ result.OpenApiDocument.Workspace.Contains("./externalResource.yaml");
+ responseSchema.Properties.Count.Should().Be(2); // reference has been resolved
+ }
+
+ [Fact]
+ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks()
+ {
+ // Arrange
+ var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath);
+
+ var settings = new OpenApiReaderSettings
+ {
+ LoadExternalRefs = true,
+ BaseUrl = new(path),
+ };
+
+ // Act
+ var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings);
+ var doc2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "externalResource.yaml")).OpenApiDocument;
+
+ var requestBodySchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Parameters.First().Schema;
+ result.OpenApiDocument.Workspace.RegisterComponents(doc2);
+
+ // Assert
+ requestBodySchema.Properties.Count.Should().Be(2); // reference has been resolved
+ }
}
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml
new file mode 100644
index 000000000..bb3755180
--- /dev/null
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml
@@ -0,0 +1,14 @@
+openapi: 3.1.0
+info:
+ title: ReferenceById
+ version: 1.0.0
+paths:
+ /resource:
+ get:
+ parameters:
+ - name: id
+ in: query
+ required: true
+ schema:
+ $ref: 'https://example.com/schemas/user.json'
+components: {}
\ No newline at end of file
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml
new file mode 100644
index 000000000..913b20e7c
--- /dev/null
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml
@@ -0,0 +1,15 @@
+openapi: 3.1.0
+info:
+ title: ReferenceById
+ version: 1.0.0
+paths:
+ /resource:
+ get:
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: './externalResource.yaml#/components/schemas/todo'
+components: {}
\ No newline at end of file
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml
new file mode 100644
index 000000000..78d6c0851
--- /dev/null
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml
@@ -0,0 +1,22 @@
+openapi: 3.1.0
+info:
+ title: ReferencedById
+ version: 1.0.0
+paths: {}
+components:
+ schemas:
+ todo:
+ type: object
+ properties:
+ id:
+ type: string
+ name:
+ type: string
+ user:
+ $id: 'https://example.com/schemas/user.json'
+ type: object
+ properties:
+ id:
+ type: string
+ name:
+ type: string
\ No newline at end of file
diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs
index ec532bed7..2d7354f78 100644
--- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs
@@ -83,8 +83,8 @@ public OpenApiPathItemReferenceTests()
_openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument;
_openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument;
_openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri);
- _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2, OpenApiSpecVersion.OpenApi3_1);
- _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2, OpenApiSpecVersion.OpenApi3_1);
+ _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2);
+ _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2);
_localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2)
{
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 7eb01a70c..00b16a254 100755
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -1512,6 +1512,7 @@ namespace Microsoft.OpenApi.Services
public bool Contains(string location) { }
public System.Uri GetDocumentId(string key) { }
public bool RegisterComponent(string location, T component) { }
+ public void RegisterComponents(Microsoft.OpenApi.Models.OpenApiDocument document) { }
public T ResolveReference(string location) { }
}
public class OperationSearch : Microsoft.OpenApi.Services.OpenApiVisitorBase