Skip to content

Commit c377232

Browse files
Merge pull request #1316 from microsoft/mk/fix-missing-response-schema-bug
Fix missing response schema bug
2 parents 5b08061 + 201e90e commit c377232

File tree

9 files changed

+167
-124
lines changed

9 files changed

+167
-124
lines changed

src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0</TargetFrameworks>
4+
<LangVersion>latest</LangVersion>
45
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
56
<PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288890</PackageIconUrl>
67
<PackageProjectUrl>https://github.com/Microsoft/OpenAPI.NET</PackageProjectUrl>

src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,35 +73,35 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P
7373
}
7474

7575
var produces = context.GetFromTempStorage<List<string>>(TempStorageKeys.OperationProduces)
76-
?? context.GetFromTempStorage<List<string>>(TempStorageKeys.GlobalProduces);
77-
if (produces != null)
76+
?? context.GetFromTempStorage<List<string>>(TempStorageKeys.GlobalProduces)
77+
?? new List<string> { "application/octet-stream" };
78+
79+
var schema = context.GetFromTempStorage<OpenApiSchema>(TempStorageKeys.ResponseSchema, response);
80+
81+
foreach (var produce in produces)
7882
{
79-
foreach (var produce in produces)
80-
{
81-
var schema = context.GetFromTempStorage<OpenApiSchema>(TempStorageKeys.ResponseSchema, response);
8283

83-
if (response.Content.ContainsKey(produce) && response.Content[produce] != null)
84+
if (response.Content.TryGetValue(produce, out var produceValue))
85+
{
86+
if (schema != null)
8487
{
85-
if (schema != null)
86-
{
87-
response.Content[produce].Schema = schema;
88-
ProcessAnyFields(mapNode, response.Content[produce], _mediaTypeAnyFields);
89-
}
88+
produceValue.Schema = schema;
89+
ProcessAnyFields(mapNode, produceValue, _mediaTypeAnyFields);
9090
}
91-
else
91+
}
92+
else
93+
{
94+
var mediaType = new OpenApiMediaType
9295
{
93-
var mediaType = new OpenApiMediaType
94-
{
95-
Schema = schema
96-
};
96+
Schema = schema
97+
};
9798

98-
response.Content.Add(produce, mediaType);
99-
}
99+
response.Content.Add(produce, mediaType);
100100
}
101-
102-
context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response);
103-
context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response);
104101
}
102+
103+
context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response);
104+
context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response);
105105
}
106106

107107
private static void LoadExamples(OpenApiResponse response, ParseNode node)

src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System.Collections.Generic;
@@ -29,10 +29,11 @@ private static void ParseMap<T>(
2929
return;
3030
}
3131

32-
foreach (var propertyNode in mapNode)
32+
var allFields = fixedFieldMap.Keys.Union(mapNode.Select(static x => x.Name));
33+
foreach (var propertyNode in allFields)
3334
{
34-
propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap);
35-
requiredFields?.Remove(propertyNode.Name);
35+
mapNode[propertyNode]?.ParseField(domainObject, fixedFieldMap, patternFieldMap);
36+
requiredFields?.Remove(propertyNode);
3637
}
3738
}
3839

@@ -77,16 +78,18 @@ private static void ProcessAnyListFields<T>(
7778
var newProperty = new List<IOpenApiAny>();
7879

7980
mapNode.Context.StartObject(anyListFieldName);
80-
81-
var list = anyListFieldMap[anyListFieldName].PropertyGetter(domainObject);
82-
if (list != null)
81+
if (anyListFieldMap.TryGetValue(anyListFieldName, out var fieldName))
8382
{
84-
foreach (var propertyElement in list)
83+
var list = fieldName.PropertyGetter(domainObject);
84+
if (list != null)
8585
{
86-
newProperty.Add(
87-
OpenApiAnyConverter.GetSpecificOpenApiAny(
88-
propertyElement,
89-
anyListFieldMap[anyListFieldName].SchemaGetter(domainObject)));
86+
foreach (var propertyElement in list)
87+
{
88+
newProperty.Add(
89+
OpenApiAnyConverter.GetSpecificOpenApiAny(
90+
propertyElement,
91+
anyListFieldMap[anyListFieldName].SchemaGetter(domainObject)));
92+
}
9093
}
9194
}
9295

@@ -104,47 +107,6 @@ private static void ProcessAnyListFields<T>(
104107
}
105108
}
106109

107-
private static void ProcessAnyMapFields<T, U>(
108-
MapNode mapNode,
109-
T domainObject,
110-
AnyMapFieldMap<T, U> anyMapFieldMap)
111-
{
112-
foreach (var anyMapFieldName in anyMapFieldMap.Keys.ToList())
113-
{
114-
try
115-
{
116-
var newProperty = new List<IOpenApiAny>();
117-
118-
mapNode.Context.StartObject(anyMapFieldName);
119-
120-
foreach (var propertyMapElement in anyMapFieldMap[anyMapFieldName].PropertyMapGetter(domainObject))
121-
{
122-
if (propertyMapElement.Value != null)
123-
{
124-
mapNode.Context.StartObject(propertyMapElement.Key);
125-
126-
var any = anyMapFieldMap[anyMapFieldName].PropertyGetter(propertyMapElement.Value);
127-
128-
var newAny = OpenApiAnyConverter.GetSpecificOpenApiAny(
129-
any,
130-
anyMapFieldMap[anyMapFieldName].SchemaGetter(domainObject));
131-
132-
anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, newAny);
133-
}
134-
}
135-
}
136-
catch (OpenApiException exception)
137-
{
138-
exception.Pointer = mapNode.Context.GetLocation();
139-
mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception));
140-
}
141-
finally
142-
{
143-
mapNode.Context.EndObject();
144-
}
145-
}
146-
}
147-
148110
public static IOpenApiAny LoadAny(ParseNode node)
149111
{
150112
return OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny());

src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ private void ResolveTags(IList<OpenApiTag> tags)
256256

257257
private T ResolveReference<T>(OpenApiReference reference) where T : class, IOpenApiReferenceable, new()
258258
{
259-
if (string.IsNullOrEmpty(reference.ExternalResource))
259+
if (string.IsNullOrEmpty(reference?.ExternalResource))
260260
{
261261
try
262262
{

test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj

Lines changed: 5 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
<PropertyGroup>
33
<TargetFrameworks>net7.0</TargetFrameworks>
44
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
@@ -51,10 +51,14 @@
5151
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithBody.yaml">
5252
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
5353
</EmbeddedResource>
54+
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithBodyAndEmptyConsumes.yaml">
55+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
56+
</EmbeddedResource>
5457
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithFormData.yaml">
5558
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
5659
</EmbeddedResource>
5760
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithResponseExamples.yaml" />
61+
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithEmptyProducesArrayInResponse.json" />
5862
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\bodyParameter.yaml">
5963
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
6064
</EmbeddedResource>

test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -163,45 +163,25 @@ public void ShouldParseProducesInAnyOrder()
163163
var reader = new OpenApiStreamReader();
164164
var doc = reader.Read(stream, out var diagnostic);
165165

166-
var successSchema = new OpenApiSchema()
166+
var okSchema = new OpenApiSchema()
167167
{
168-
Type = "array",
169168
Reference = new OpenApiReference
170169
{
171170
Type = ReferenceType.Schema,
172171
Id = "Item",
173172
HostDocument = doc
174173
},
175-
Items = new OpenApiSchema()
174+
Properties = new Dictionary<string, OpenApiSchema>()
176175
{
177-
Reference = new OpenApiReference()
178-
{
179-
Type = ReferenceType.Schema,
180-
Id = "Item",
181-
HostDocument = doc
176+
{ "id", new OpenApiSchema()
177+
{
178+
Type = "string",
179+
Description = "Item identifier."
180+
}
182181
}
183182
}
184183
};
185184

186-
var okSchema = new OpenApiSchema()
187-
{
188-
Reference = new OpenApiReference
189-
{
190-
Type = ReferenceType.Schema,
191-
Id = "Item",
192-
HostDocument = doc
193-
},
194-
Properties = new Dictionary<string, OpenApiSchema>()
195-
{
196-
{ "id", new OpenApiSchema()
197-
{
198-
Type = "string",
199-
Description = "Item identifier."
200-
}
201-
}
202-
}
203-
};
204-
205185
var errorSchema = new OpenApiSchema()
206186
{
207187
Reference = new OpenApiReference
@@ -211,24 +191,24 @@ public void ShouldParseProducesInAnyOrder()
211191
HostDocument = doc
212192
},
213193
Properties = new Dictionary<string, OpenApiSchema>()
214-
{
215-
{ "code", new OpenApiSchema()
216-
{
217-
Type = "integer",
218-
Format = "int32"
219-
}
220-
},
221-
{ "message", new OpenApiSchema()
222-
{
223-
Type = "string"
224-
}
225-
},
226-
{ "fields", new OpenApiSchema()
227-
{
228-
Type = "string"
229-
}
230-
}
231-
}
194+
{
195+
{ "code", new OpenApiSchema()
196+
{
197+
Type = "integer",
198+
Format = "int32"
199+
}
200+
},
201+
{ "message", new OpenApiSchema()
202+
{
203+
Type = "string"
204+
}
205+
},
206+
{ "fields", new OpenApiSchema()
207+
{
208+
Type = "string"
209+
}
210+
}
211+
}
232212
};
233213

234214
var okMediaType = new OpenApiMediaType
@@ -439,7 +419,7 @@ public void ShouldAllowComponentsThatJustContainAReference()
439419
if (schema2.UnresolvedReference && schema1.Reference.Id == schema2.Reference.Id)
440420
{
441421
// detected a cycle - this code gets triggered
442-
Assert.True(false, "A cycle should not be detected");
422+
Assert.Fail("A cycle should not be detected");
443423
}
444424
}
445425
}

test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ public class OpenApiOperationTests
182182
}
183183
}
184184
},
185-
Extensions = {
186-
[OpenApiConstants.BodyName] = new OpenApiString("petObject")
185+
Extensions = {
186+
[OpenApiConstants.BodyName] = new OpenApiString("petObject")
187187
}
188188
},
189189
Responses = new OpenApiResponses
@@ -375,5 +375,62 @@ public void ParseOperationWithResponseExamplesShouldSucceed()
375375
}
376376
);
377377
}
378+
379+
[Fact]
380+
public void ParseOperationWithEmptyProducesArraySetsResponseSchemaIfExists()
381+
{
382+
// Arrange
383+
MapNode node;
384+
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithEmptyProducesArrayInResponse.json"));
385+
node = TestHelper.CreateYamlMapNode(stream);
386+
387+
// Act
388+
var operation = OpenApiV2Deserializer.LoadOperation(node);
389+
390+
// Assert
391+
operation.Should().BeEquivalentTo(
392+
new OpenApiOperation()
393+
{
394+
Responses = new OpenApiResponses()
395+
{
396+
{ "200", new OpenApiResponse()
397+
{
398+
Description = "OK",
399+
Content =
400+
{
401+
["application/octet-stream"] = new OpenApiMediaType()
402+
{
403+
Schema = new OpenApiSchema()
404+
{
405+
Format = "binary",
406+
Description = "The content of the file.",
407+
Type = "string",
408+
Extensions =
409+
{
410+
["x-ms-summary"] = new OpenApiString("File Content")
411+
}
412+
}
413+
}
414+
}
415+
}}
416+
}
417+
}
418+
);
419+
}
420+
421+
[Fact]
422+
public void ParseOperationWithBodyAndEmptyConsumesSetsRequestBodySchemaIfExists()
423+
{
424+
// Arrange
425+
MapNode node;
426+
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithBodyAndEmptyConsumes.yaml"));
427+
node = TestHelper.CreateYamlMapNode(stream);
428+
429+
// Act
430+
var operation = OpenApiV2Deserializer.LoadOperation(node);
431+
432+
// Assert
433+
operation.Should().BeEquivalentTo(_operationWithBody);
434+
}
378435
}
379436
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Modified from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object-example
2+
summary: Updates a pet in the store with request body
3+
description: ""
4+
operationId: updatePetWithBody
5+
consumes: []
6+
produces:
7+
- application/json
8+
- application/xml
9+
parameters:
10+
- name: petId
11+
in: path
12+
description: ID of pet that needs to be updated
13+
required: true
14+
type: string
15+
- name: petObject
16+
in: body
17+
description: Pet to update with
18+
required: true
19+
schema:
20+
type: object
21+
responses:
22+
'200':
23+
description: Pet updated.
24+
'405':
25+
description: Invalid input

0 commit comments

Comments
 (0)