Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy over references for all IOpenApiReferenceable objects #1803

Merged
merged 4 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"

Loading