Skip to content

Commit ef4de8b

Browse files
Merge pull request #1803 from microsoft/mk/fix-reference-copying
Copy over references for all IOpenApiReferenceable objects
2 parents ee3bf93 + 6db0216 commit ef4de8b

File tree

5 files changed

+242
-33
lines changed

5 files changed

+242
-33
lines changed

src/Microsoft.OpenApi/Services/CopyReferences.cs

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,91 @@ public override void Visit(IOpenApiReferenceable referenceable)
2626
switch (referenceable)
2727
{
2828
case OpenApiSchema schema:
29-
EnsureComponentsExists();
30-
EnsureSchemasExists();
29+
EnsureComponentsExist();
30+
EnsureSchemasExist();
3131
if (!Components.Schemas.ContainsKey(schema.Reference.Id))
3232
{
3333
Components.Schemas.Add(schema.Reference.Id, schema);
3434
}
3535
break;
3636

3737
case OpenApiParameter parameter:
38-
EnsureComponentsExists();
39-
EnsureParametersExists();
38+
EnsureComponentsExist();
39+
EnsureParametersExist();
4040
if (!Components.Parameters.ContainsKey(parameter.Reference.Id))
4141
{
4242
Components.Parameters.Add(parameter.Reference.Id, parameter);
4343
}
4444
break;
4545

4646
case OpenApiResponse response:
47-
EnsureComponentsExists();
48-
EnsureResponsesExists();
47+
EnsureComponentsExist();
48+
EnsureResponsesExist();
4949
if (!Components.Responses.ContainsKey(response.Reference.Id))
5050
{
5151
Components.Responses.Add(response.Reference.Id, response);
5252
}
5353
break;
5454

5555
case OpenApiRequestBody requestBody:
56-
EnsureComponentsExists();
57-
EnsureResponsesExists();
58-
EnsurRequestBodiesExists();
56+
EnsureComponentsExist();
57+
EnsureResponsesExist();
58+
EnsureRequestBodiesExist();
5959
if (!Components.RequestBodies.ContainsKey(requestBody.Reference.Id))
6060
{
6161
Components.RequestBodies.Add(requestBody.Reference.Id, requestBody);
6262
}
6363
break;
6464

65+
case OpenApiExample example:
66+
EnsureComponentsExist();
67+
EnsureExamplesExist();
68+
if (!Components.Examples.ContainsKey(example.Reference.Id))
69+
{
70+
Components.Examples.Add(example.Reference.Id, example);
71+
}
72+
break;
73+
74+
case OpenApiHeader header:
75+
EnsureComponentsExist();
76+
EnsureHeadersExist();
77+
if (!Components.Headers.ContainsKey(header.Reference.Id))
78+
{
79+
Components.Headers.Add(header.Reference.Id, header);
80+
}
81+
break;
82+
83+
case OpenApiCallback callback:
84+
EnsureComponentsExist();
85+
EnsureCallbacksExist();
86+
if (!Components.Callbacks.ContainsKey(callback.Reference.Id))
87+
{
88+
Components.Callbacks.Add(callback.Reference.Id, callback);
89+
}
90+
break;
91+
92+
case OpenApiLink link:
93+
EnsureComponentsExist();
94+
EnsureLinksExist();
95+
if (!Components.Links.ContainsKey(link.Reference.Id))
96+
{
97+
Components.Links.Add(link.Reference.Id, link);
98+
}
99+
break;
100+
101+
case OpenApiSecurityScheme securityScheme:
102+
EnsureComponentsExist();
103+
EnsureSecuritySchemesExist();
104+
if (!Components.SecuritySchemes.ContainsKey(securityScheme.Reference.Id))
105+
{
106+
Components.SecuritySchemes.Add(securityScheme.Reference.Id, securityScheme);
107+
}
108+
break;
109+
65110
default:
66111
break;
67112
}
113+
68114
base.Visit(referenceable);
69115
}
70116

@@ -77,8 +123,8 @@ public override void Visit(OpenApiSchema schema)
77123
// This is needed to handle schemas used in Responses in components
78124
if (schema.Reference != null)
79125
{
80-
EnsureComponentsExists();
81-
EnsureSchemasExists();
126+
EnsureComponentsExist();
127+
EnsureSchemasExist();
82128
if (!Components.Schemas.ContainsKey(schema.Reference.Id))
83129
{
84130
Components.Schemas.Add(schema.Reference.Id, schema);
@@ -87,41 +133,54 @@ public override void Visit(OpenApiSchema schema)
87133
base.Visit(schema);
88134
}
89135

90-
private void EnsureComponentsExists()
136+
private void EnsureComponentsExist()
91137
{
92-
if (_target.Components == null)
93-
{
94-
_target.Components = new();
95-
}
138+
_target.Components ??= new();
96139
}
97140

98-
private void EnsureSchemasExists()
141+
private void EnsureSchemasExist()
99142
{
100-
if (_target.Components.Schemas == null)
101-
{
102-
_target.Components.Schemas = new Dictionary<string, OpenApiSchema>();
103-
}
143+
_target.Components.Schemas ??= new Dictionary<string, OpenApiSchema>();
104144
}
105145

106-
private void EnsureParametersExists()
146+
private void EnsureParametersExist()
107147
{
108-
if (_target.Components.Parameters == null)
109-
{
110-
_target.Components.Parameters = new Dictionary<string, OpenApiParameter>();
111-
}
148+
_target.Components.Parameters ??= new Dictionary<string, OpenApiParameter>();
112149
}
113150

114-
private void EnsureResponsesExists()
151+
private void EnsureResponsesExist()
115152
{
116-
if (_target.Components.Responses == null)
117-
{
118-
_target.Components.Responses = new Dictionary<string, OpenApiResponse>();
119-
}
153+
_target.Components.Responses ??= new Dictionary<string, OpenApiResponse>();
120154
}
121155

122-
private void EnsurRequestBodiesExists()
156+
private void EnsureRequestBodiesExist()
123157
{
124158
_target.Components.RequestBodies ??= new Dictionary<string, OpenApiRequestBody>();
125159
}
160+
161+
private void EnsureExamplesExist()
162+
{
163+
_target.Components.Examples ??= new Dictionary<string, OpenApiExample>();
164+
}
165+
166+
private void EnsureHeadersExist()
167+
{
168+
_target.Components.Headers ??= new Dictionary<string, OpenApiHeader>();
169+
}
170+
171+
private void EnsureCallbacksExist()
172+
{
173+
_target.Components.Callbacks ??= new Dictionary<string, OpenApiCallback>();
174+
}
175+
176+
private void EnsureLinksExist()
177+
{
178+
_target.Components.Links ??= new Dictionary<string, OpenApiLink>();
179+
}
180+
181+
private void EnsureSecuritySchemesExist()
182+
{
183+
_target.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();
184+
}
126185
}
127186
}

src/Microsoft.OpenApi/Services/OpenApiFilterService.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,42 @@ private static bool AddReferences(OpenApiComponents newComponents, OpenApiCompon
278278
moreStuff = true;
279279
target.RequestBodies.Add(item);
280280
}
281+
282+
foreach (var item in newComponents.Headers
283+
.Where(item => !target.Headers.ContainsKey(item.Key)))
284+
{
285+
moreStuff = true;
286+
target.Headers.Add(item);
287+
}
288+
289+
foreach (var item in newComponents.Links
290+
.Where(item => !target.Links.ContainsKey(item.Key)))
291+
{
292+
moreStuff = true;
293+
target.Links.Add(item);
294+
}
295+
296+
foreach (var item in newComponents.Callbacks
297+
.Where(item => !target.Callbacks.ContainsKey(item.Key)))
298+
{
299+
moreStuff = true;
300+
target.Callbacks.Add(item);
301+
}
302+
303+
foreach (var item in newComponents.Examples
304+
.Where(item => !target.Examples.ContainsKey(item.Key)))
305+
{
306+
moreStuff = true;
307+
target.Examples.Add(item);
308+
}
309+
310+
foreach (var item in newComponents.SecuritySchemes
311+
.Where(item => !target.SecuritySchemes.ContainsKey(item.Key)))
312+
{
313+
moreStuff = true;
314+
target.SecuritySchemes.Add(item);
315+
}
316+
281317
return moreStuff;
282318
}
283319

test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net8.0</TargetFramework>
@@ -34,4 +34,10 @@
3434
</Compile>
3535
</ItemGroup>
3636

37+
<ItemGroup>
38+
<None Update="UtilityFiles\docWithReusableHeadersAndExamples.yaml">
39+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
40+
</None>
41+
</ItemGroup>
42+
3743
</Project>

test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using Microsoft.Extensions.Logging;
55
using Microsoft.OpenApi.Models;
6+
using Microsoft.OpenApi.Readers;
67
using Microsoft.OpenApi.Services;
78
using Microsoft.OpenApi.Tests.UtilityFiles;
89
using Moq;
10+
using SharpYaml.Tokens;
911
using Xunit;
1012

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

175+
[Fact]
176+
public void CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly()
177+
{
178+
// Arrange
179+
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "docWithReusableHeadersAndExamples.yaml");
180+
var operationIds = "getItems";
181+
182+
// Act
183+
using var stream = File.OpenRead(filePath);
184+
var doc = new OpenApiStreamReader().Read(stream, out var diagnostic);
185+
186+
var predicate = OpenApiFilterService.CreatePredicate(operationIds: operationIds);
187+
var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(doc, predicate);
188+
189+
var response = subsetOpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses["200"];
190+
var responseHeader = response.Headers["x-custom-header"];
191+
var mediaTypeExample = response.Content["application/json"].Examples.First().Value;
192+
var targetHeaders = subsetOpenApiDocument.Components.Headers;
193+
var targetExamples = subsetOpenApiDocument.Components.Examples;
194+
195+
// Assert
196+
Assert.False(responseHeader.UnresolvedReference);
197+
Assert.False(mediaTypeExample.UnresolvedReference);
198+
Assert.Single(targetHeaders);
199+
Assert.Single(targetExamples);
200+
}
201+
173202
[Theory]
174203
[InlineData("reports.getTeamsUserActivityUserDetail-a3f1", null)]
175204
[InlineData(null, "reports.Functions")]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
openapi: 3.0.1
2+
info:
3+
title: Example with Multiple Operations and Local $refs
4+
version: 1.0.0
5+
paths:
6+
/items:
7+
get:
8+
operationId: getItems
9+
summary: Get a list of items
10+
responses:
11+
'200':
12+
description: A list of items
13+
headers:
14+
x-custom-header:
15+
$ref: '#/components/headers/CustomHeader'
16+
content:
17+
application/json:
18+
schema:
19+
type: array
20+
items:
21+
type: string
22+
examples:
23+
ItemExample:
24+
$ref: '#/components/examples/ItemExample'
25+
post:
26+
operationId: createItem
27+
summary: Create a new item
28+
requestBody:
29+
required: true
30+
content:
31+
application/json:
32+
schema:
33+
type: object
34+
properties:
35+
name:
36+
type: string
37+
example:
38+
$ref: '#/components/examples/ItemExample'
39+
responses:
40+
'201':
41+
description: Item created
42+
content:
43+
application/json:
44+
schema:
45+
type: object
46+
properties:
47+
id:
48+
type: string
49+
name:
50+
type: string
51+
example:
52+
$ref: '#/components/examples/ItemExample'
53+
components:
54+
schemas:
55+
pet:
56+
type: object
57+
required:
58+
- id
59+
- name
60+
properties:
61+
id:
62+
type: integer
63+
format: int64
64+
name:
65+
type: string
66+
tag:
67+
type: string
68+
headers:
69+
CustomHeader:
70+
description: Custom header for authentication
71+
required: true
72+
schema:
73+
type: string
74+
examples:
75+
ItemExample:
76+
summary: Example of a new item to be created
77+
value:
78+
name: "New Item"
79+

0 commit comments

Comments
 (0)