Skip to content

Commit

Permalink
Merge pull request #1803 from microsoft/mk/fix-reference-copying
Browse files Browse the repository at this point in the history
Copy over references for all IOpenApiReferenceable objects
  • Loading branch information
MaggieKimani1 authored Aug 30, 2024
2 parents ee3bf93 + 6db0216 commit ef4de8b
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 33 deletions.
123 changes: 91 additions & 32 deletions src/Microsoft.OpenApi/Services/CopyReferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,45 +26,91 @@ public override void Visit(IOpenApiReferenceable referenceable)
switch (referenceable)
{
case OpenApiSchema schema:
EnsureComponentsExists();
EnsureSchemasExists();
EnsureComponentsExist();
EnsureSchemasExist();
if (!Components.Schemas.ContainsKey(schema.Reference.Id))
{
Components.Schemas.Add(schema.Reference.Id, schema);
}
break;

case OpenApiParameter parameter:
EnsureComponentsExists();
EnsureParametersExists();
EnsureComponentsExist();
EnsureParametersExist();
if (!Components.Parameters.ContainsKey(parameter.Reference.Id))
{
Components.Parameters.Add(parameter.Reference.Id, parameter);
}
break;

case OpenApiResponse response:
EnsureComponentsExists();
EnsureResponsesExists();
EnsureComponentsExist();
EnsureResponsesExist();
if (!Components.Responses.ContainsKey(response.Reference.Id))
{
Components.Responses.Add(response.Reference.Id, response);
}
break;

case OpenApiRequestBody requestBody:
EnsureComponentsExists();
EnsureResponsesExists();
EnsurRequestBodiesExists();
EnsureComponentsExist();
EnsureResponsesExist();
EnsureRequestBodiesExist();
if (!Components.RequestBodies.ContainsKey(requestBody.Reference.Id))
{
Components.RequestBodies.Add(requestBody.Reference.Id, requestBody);
}
break;

case OpenApiExample example:
EnsureComponentsExist();
EnsureExamplesExist();
if (!Components.Examples.ContainsKey(example.Reference.Id))
{
Components.Examples.Add(example.Reference.Id, example);
}
break;

case OpenApiHeader header:
EnsureComponentsExist();
EnsureHeadersExist();
if (!Components.Headers.ContainsKey(header.Reference.Id))
{
Components.Headers.Add(header.Reference.Id, header);
}
break;

case OpenApiCallback callback:
EnsureComponentsExist();
EnsureCallbacksExist();
if (!Components.Callbacks.ContainsKey(callback.Reference.Id))
{
Components.Callbacks.Add(callback.Reference.Id, callback);
}
break;

case OpenApiLink link:
EnsureComponentsExist();
EnsureLinksExist();
if (!Components.Links.ContainsKey(link.Reference.Id))
{
Components.Links.Add(link.Reference.Id, link);
}
break;

case OpenApiSecurityScheme securityScheme:
EnsureComponentsExist();
EnsureSecuritySchemesExist();
if (!Components.SecuritySchemes.ContainsKey(securityScheme.Reference.Id))
{
Components.SecuritySchemes.Add(securityScheme.Reference.Id, securityScheme);
}
break;

default:
break;
}

base.Visit(referenceable);
}

Expand All @@ -77,8 +123,8 @@ public override void Visit(OpenApiSchema schema)
// This is needed to handle schemas used in Responses in components
if (schema.Reference != null)
{
EnsureComponentsExists();
EnsureSchemasExists();
EnsureComponentsExist();
EnsureSchemasExist();
if (!Components.Schemas.ContainsKey(schema.Reference.Id))
{
Components.Schemas.Add(schema.Reference.Id, schema);
Expand All @@ -87,41 +133,54 @@ public override void Visit(OpenApiSchema schema)
base.Visit(schema);
}

private void EnsureComponentsExists()
private void EnsureComponentsExist()
{
if (_target.Components == null)
{
_target.Components = new();
}
_target.Components ??= new();
}

private void EnsureSchemasExists()
private void EnsureSchemasExist()
{
if (_target.Components.Schemas == null)
{
_target.Components.Schemas = new Dictionary<string, OpenApiSchema>();
}
_target.Components.Schemas ??= new Dictionary<string, OpenApiSchema>();
}

private void EnsureParametersExists()
private void EnsureParametersExist()
{
if (_target.Components.Parameters == null)
{
_target.Components.Parameters = new Dictionary<string, OpenApiParameter>();
}
_target.Components.Parameters ??= new Dictionary<string, OpenApiParameter>();
}

private void EnsureResponsesExists()
private void EnsureResponsesExist()
{
if (_target.Components.Responses == null)
{
_target.Components.Responses = new Dictionary<string, OpenApiResponse>();
}
_target.Components.Responses ??= new Dictionary<string, OpenApiResponse>();
}

private void EnsurRequestBodiesExists()
private void EnsureRequestBodiesExist()
{
_target.Components.RequestBodies ??= new Dictionary<string, OpenApiRequestBody>();
}

private void EnsureExamplesExist()
{
_target.Components.Examples ??= new Dictionary<string, OpenApiExample>();
}

private void EnsureHeadersExist()
{
_target.Components.Headers ??= new Dictionary<string, OpenApiHeader>();
}

private void EnsureCallbacksExist()
{
_target.Components.Callbacks ??= new Dictionary<string, OpenApiCallback>();
}

private void EnsureLinksExist()
{
_target.Components.Links ??= new Dictionary<string, OpenApiLink>();
}

private void EnsureSecuritySchemesExist()
{
_target.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();
}
}
}
36 changes: 36 additions & 0 deletions src/Microsoft.OpenApi/Services/OpenApiFilterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,42 @@ private static bool AddReferences(OpenApiComponents newComponents, OpenApiCompon
moreStuff = true;
target.RequestBodies.Add(item);
}

foreach (var item in newComponents.Headers
.Where(item => !target.Headers.ContainsKey(item.Key)))
{
moreStuff = true;
target.Headers.Add(item);
}

foreach (var item in newComponents.Links
.Where(item => !target.Links.ContainsKey(item.Key)))
{
moreStuff = true;
target.Links.Add(item);
}

foreach (var item in newComponents.Callbacks
.Where(item => !target.Callbacks.ContainsKey(item.Key)))
{
moreStuff = true;
target.Callbacks.Add(item);
}

foreach (var item in newComponents.Examples
.Where(item => !target.Examples.ContainsKey(item.Key)))
{
moreStuff = true;
target.Examples.Add(item);
}

foreach (var item in newComponents.SecuritySchemes
.Where(item => !target.SecuritySchemes.ContainsKey(item.Key)))
{
moreStuff = true;
target.SecuritySchemes.Add(item);
}

return moreStuff;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand Down Expand Up @@ -34,4 +34,10 @@
</Compile>
</ItemGroup>

<ItemGroup>
<None Update="UtilityFiles\docWithReusableHeadersAndExamples.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using Microsoft.OpenApi.Services;
using Microsoft.OpenApi.Tests.UtilityFiles;
using Moq;
using SharpYaml.Tokens;
using Xunit;

namespace Microsoft.OpenApi.Hidi.Tests
Expand Down Expand Up @@ -170,6 +172,33 @@ public void ThrowsInvalidOperationExceptionInCreatePredicateWhenInvalidArguments
Assert.Equal("Cannot specify both operationIds and tags at the same time.", message2);
}

[Fact]
public void CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly()
{
// Arrange
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "docWithReusableHeadersAndExamples.yaml");
var operationIds = "getItems";

// Act
using var stream = File.OpenRead(filePath);
var doc = new OpenApiStreamReader().Read(stream, out var diagnostic);

var predicate = OpenApiFilterService.CreatePredicate(operationIds: operationIds);
var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(doc, predicate);

var response = subsetOpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses["200"];
var responseHeader = response.Headers["x-custom-header"];
var mediaTypeExample = response.Content["application/json"].Examples.First().Value;
var targetHeaders = subsetOpenApiDocument.Components.Headers;
var targetExamples = subsetOpenApiDocument.Components.Examples;

// Assert
Assert.False(responseHeader.UnresolvedReference);
Assert.False(mediaTypeExample.UnresolvedReference);
Assert.Single(targetHeaders);
Assert.Single(targetExamples);
}

[Theory]
[InlineData("reports.getTeamsUserActivityUserDetail-a3f1", null)]
[InlineData(null, "reports.Functions")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
openapi: 3.0.1
info:
title: Example with Multiple Operations and Local $refs
version: 1.0.0
paths:
/items:
get:
operationId: getItems
summary: Get a list of items
responses:
'200':
description: A list of items
headers:
x-custom-header:
$ref: '#/components/headers/CustomHeader'
content:
application/json:
schema:
type: array
items:
type: string
examples:
ItemExample:
$ref: '#/components/examples/ItemExample'
post:
operationId: createItem
summary: Create a new item
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
example:
$ref: '#/components/examples/ItemExample'
responses:
'201':
description: Item created
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: string
example:
$ref: '#/components/examples/ItemExample'
components:
schemas:
pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
headers:
CustomHeader:
description: Custom header for authentication
required: true
schema:
type: string
examples:
ItemExample:
summary: Example of a new item to be created
value:
name: "New Item"

0 comments on commit ef4de8b

Please sign in to comment.