diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index ba2cf568d..b7bdc3ac3 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -1,6 +1,7 @@  netstandard2.0 + latest true http://go.microsoft.com/fwlink/?LinkID=288890 https://github.com/Microsoft/OpenAPI.NET diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index 343dcd2ce..5734ff19d 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -73,35 +73,35 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P } var produces = context.GetFromTempStorage>(TempStorageKeys.OperationProduces) - ?? context.GetFromTempStorage>(TempStorageKeys.GlobalProduces); - if (produces != null) + ?? context.GetFromTempStorage>(TempStorageKeys.GlobalProduces) + ?? new List { "application/octet-stream" }; + + var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); + + foreach (var produce in produces) { - foreach (var produce in produces) - { - var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); - if (response.Content.ContainsKey(produce) && response.Content[produce] != null) + if (response.Content.TryGetValue(produce, out var produceValue)) + { + if (schema != null) { - if (schema != null) - { - response.Content[produce].Schema = schema; - ProcessAnyFields(mapNode, response.Content[produce], _mediaTypeAnyFields); - } + produceValue.Schema = schema; + ProcessAnyFields(mapNode, produceValue, _mediaTypeAnyFields); } - else + } + else + { + var mediaType = new OpenApiMediaType { - var mediaType = new OpenApiMediaType - { - Schema = schema - }; + Schema = schema + }; - response.Content.Add(produce, mediaType); - } + response.Content.Add(produce, mediaType); } - - context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response); - context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response); } + + context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response); + context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response); } private static void LoadExamples(OpenApiResponse response, ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs index 4bb15a8d9..c3f26b896 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Collections.Generic; @@ -29,10 +29,11 @@ private static void ParseMap( return; } - foreach (var propertyNode in mapNode) + var allFields = fixedFieldMap.Keys.Union(mapNode.Select(static x => x.Name)); + foreach (var propertyNode in allFields) { - propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap); - requiredFields?.Remove(propertyNode.Name); + mapNode[propertyNode]?.ParseField(domainObject, fixedFieldMap, patternFieldMap); + requiredFields?.Remove(propertyNode); } } @@ -77,16 +78,18 @@ private static void ProcessAnyListFields( var newProperty = new List(); mapNode.Context.StartObject(anyListFieldName); - - var list = anyListFieldMap[anyListFieldName].PropertyGetter(domainObject); - if (list != null) + if (anyListFieldMap.TryGetValue(anyListFieldName, out var fieldName)) { - foreach (var propertyElement in list) + var list = fieldName.PropertyGetter(domainObject); + if (list != null) { - newProperty.Add( - OpenApiAnyConverter.GetSpecificOpenApiAny( - propertyElement, - anyListFieldMap[anyListFieldName].SchemaGetter(domainObject))); + foreach (var propertyElement in list) + { + newProperty.Add( + OpenApiAnyConverter.GetSpecificOpenApiAny( + propertyElement, + anyListFieldMap[anyListFieldName].SchemaGetter(domainObject))); + } } } @@ -104,47 +107,6 @@ private static void ProcessAnyListFields( } } - private static void ProcessAnyMapFields( - MapNode mapNode, - T domainObject, - AnyMapFieldMap anyMapFieldMap) - { - foreach (var anyMapFieldName in anyMapFieldMap.Keys.ToList()) - { - try - { - var newProperty = new List(); - - mapNode.Context.StartObject(anyMapFieldName); - - foreach (var propertyMapElement in anyMapFieldMap[anyMapFieldName].PropertyMapGetter(domainObject)) - { - if (propertyMapElement.Value != null) - { - mapNode.Context.StartObject(propertyMapElement.Key); - - var any = anyMapFieldMap[anyMapFieldName].PropertyGetter(propertyMapElement.Value); - - var newAny = OpenApiAnyConverter.GetSpecificOpenApiAny( - any, - anyMapFieldMap[anyMapFieldName].SchemaGetter(domainObject)); - - anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, newAny); - } - } - } - catch (OpenApiException exception) - { - exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); - } - finally - { - mapNode.Context.EndObject(); - } - } - } - public static IOpenApiAny LoadAny(ParseNode node) { return OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()); diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index feeceb9af..3b431d4b5 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs @@ -256,7 +256,7 @@ private void ResolveTags(IList tags) private T ResolveReference(OpenApiReference reference) where T : class, IOpenApiReferenceable, new() { - if (string.IsNullOrEmpty(reference.ExternalResource)) + if (string.IsNullOrEmpty(reference?.ExternalResource)) { try { diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 4f55e76ee..98ac3a206 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -1,4 +1,4 @@ - + net7.0 false @@ -51,10 +51,14 @@ Never + + Never + Never + Never diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index fcf0471ea..0b35d43e8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -163,45 +163,25 @@ public void ShouldParseProducesInAnyOrder() var reader = new OpenApiStreamReader(); var doc = reader.Read(stream, out var diagnostic); - var successSchema = new OpenApiSchema() + var okSchema = new OpenApiSchema() { - Type = "array", Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Item", HostDocument = doc }, - Items = new OpenApiSchema() + Properties = new Dictionary() { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "Item", - HostDocument = doc + { "id", new OpenApiSchema() + { + Type = "string", + Description = "Item identifier." + } } } }; - var okSchema = new OpenApiSchema() - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "Item", - HostDocument = doc - }, - Properties = new Dictionary() - { - { "id", new OpenApiSchema() - { - Type = "string", - Description = "Item identifier." - } - } - } - }; - var errorSchema = new OpenApiSchema() { Reference = new OpenApiReference @@ -211,24 +191,24 @@ public void ShouldParseProducesInAnyOrder() HostDocument = doc }, Properties = new Dictionary() - { - { "code", new OpenApiSchema() - { - Type = "integer", - Format = "int32" - } - }, - { "message", new OpenApiSchema() - { - Type = "string" - } - }, - { "fields", new OpenApiSchema() - { - Type = "string" - } - } - } + { + { "code", new OpenApiSchema() + { + Type = "integer", + Format = "int32" + } + }, + { "message", new OpenApiSchema() + { + Type = "string" + } + }, + { "fields", new OpenApiSchema() + { + Type = "string" + } + } + } }; var okMediaType = new OpenApiMediaType @@ -439,7 +419,7 @@ public void ShouldAllowComponentsThatJustContainAReference() if (schema2.UnresolvedReference && schema1.Reference.Id == schema2.Reference.Id) { // detected a cycle - this code gets triggered - Assert.True(false, "A cycle should not be detected"); + Assert.Fail("A cycle should not be detected"); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 0deb72a5c..3b0f32871 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -182,8 +182,8 @@ public class OpenApiOperationTests } } }, - Extensions = { - [OpenApiConstants.BodyName] = new OpenApiString("petObject") + Extensions = { + [OpenApiConstants.BodyName] = new OpenApiString("petObject") } }, Responses = new OpenApiResponses @@ -375,5 +375,62 @@ public void ParseOperationWithResponseExamplesShouldSucceed() } ); } + + [Fact] + public void ParseOperationWithEmptyProducesArraySetsResponseSchemaIfExists() + { + // Arrange + MapNode node; + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithEmptyProducesArrayInResponse.json")); + node = TestHelper.CreateYamlMapNode(stream); + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.Should().BeEquivalentTo( + new OpenApiOperation() + { + Responses = new OpenApiResponses() + { + { "200", new OpenApiResponse() + { + Description = "OK", + Content = + { + ["application/octet-stream"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema() + { + Format = "binary", + Description = "The content of the file.", + Type = "string", + Extensions = + { + ["x-ms-summary"] = new OpenApiString("File Content") + } + } + } + } + }} + } + } + ); + } + + [Fact] + public void ParseOperationWithBodyAndEmptyConsumesSetsRequestBodySchemaIfExists() + { + // Arrange + MapNode node; + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithBodyAndEmptyConsumes.yaml")); + node = TestHelper.CreateYamlMapNode(stream); + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.Should().BeEquivalentTo(_operationWithBody); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBodyAndEmptyConsumes.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBodyAndEmptyConsumes.yaml new file mode 100644 index 000000000..802ec9cf7 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBodyAndEmptyConsumes.yaml @@ -0,0 +1,25 @@ +# Modified from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object-example +summary: Updates a pet in the store with request body +description: "" +operationId: updatePetWithBody +consumes: [] +produces: +- application/json +- application/xml +parameters: +- name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: string +- name: petObject + in: body + description: Pet to update with + required: true + schema: + type: object +responses: + '200': + description: Pet updated. + '405': + description: Invalid input diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithEmptyProducesArrayInResponse.json b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithEmptyProducesArrayInResponse.json new file mode 100644 index 000000000..d1495fe44 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithEmptyProducesArrayInResponse.json @@ -0,0 +1,14 @@ +{ + "produces": [], + "responses": { + "200": { + "description": "OK", + "schema": { + "format": "binary", + "description": "The content of the file.", + "type": "string", + "x-ms-summary": "File Content" + } + } + } +} \ No newline at end of file