diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs index 207845c7f..4bd28a18d 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs @@ -23,8 +23,18 @@ internal static partial class OpenApiV2Deserializer new() { { - "tags", (o, n, doc) => { - if (n.CreateSimpleList((valueNode, doc) => LoadTagByReference(valueNode.GetScalarValue(), doc), doc) is {Count: > 0} tags) + "tags", (o, n, doc) => { + if (n.CreateSimpleList( + (valueNode, doc) => + { + var val = valueNode.GetScalarValue(); + if (string.IsNullOrEmpty(val)) + return null; // Avoid exception on empty tag, we'll remove these from the list further on + return LoadTagByReference(val , doc); + }, + doc) + // Filter out empty tags instead of excepting on them + .OfType().ToList() is {Count: > 0} tags) { o.Tags = new HashSet(tags, OpenApiTagComparer.Instance); } diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs index 00fdeb3ee..eb971da7c 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models.References; @@ -20,8 +21,18 @@ internal static partial class OpenApiV3Deserializer new() { { - "tags", (o, n, doc) => { - if (n.CreateSimpleList((valueNode, doc) => LoadTagByReference(valueNode.GetScalarValue(), doc), doc) is {Count: > 0} tags) + "tags", (o, n, doc) => { + if (n.CreateSimpleList( + (valueNode, doc) => + { + var val = valueNode.GetScalarValue(); + if (string.IsNullOrEmpty(val)) + return null; // Avoid exception on empty tag, we'll remove these from the list further on + return LoadTagByReference(val , doc); + }, + doc) + // Filter out empty tags instead of excepting on them + .OfType().ToList() is {Count: > 0} tags) { o.Tags = new HashSet(tags, OpenApiTagComparer.Instance); } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs index cf0b4856c..abf35545b 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models.References; @@ -17,8 +18,18 @@ internal static partial class OpenApiV31Deserializer new() { { - "tags", (o, n, doc) => { - if (n.CreateSimpleList((valueNode, doc) => LoadTagByReference(valueNode.GetScalarValue(), doc), doc) is {Count: > 0} tags) + "tags", (o, n, doc) => { + if (n.CreateSimpleList( + (valueNode, doc) => + { + var val = valueNode.GetScalarValue(); + if (string.IsNullOrEmpty(val)) + return null; // Avoid exception on empty tag, we'll remove these from the list further on + return LoadTagByReference(val , doc); + }, + doc) + // Filter out empty tags instead of excepting on them + .OfType().ToList() is {Count: > 0} tags) { o.Tags = new HashSet(tags, OpenApiTagComparer.Instance); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 2cab4c37b..167d59cc7 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -570,6 +570,15 @@ public async Task ParseDocumentWith31PropertiesWorks() await Verifier.Verify(actual); } + [Fact] + public async Task ParseDocumentWithEmptyTagsWorks() + { + var path = Path.Combine(SampleFolderPath, "documentWithEmptyTags.json"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + + doc.Paths["/groups"].Operations[HttpMethod.Get].Tags.Should().BeNull("Empty tags are ignored, so we should not have any tags"); + } + [Fact] public void ParseEmptyMemoryStreamThrowsAnArgumentException() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithEmptyTags.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithEmptyTags.json new file mode 100644 index 000000000..d9d5d4290 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithEmptyTags.json @@ -0,0 +1,50 @@ +{ + "openapi": "3.1.0", + "info": { + "description": "Groups API", + "title": "Groups", + "version": "1.0" + }, + "paths": { + "/groups": { + "get": { + "operationId": "getGroups", + "parameters": [ + { + "description": "Zero-based page index (0..N)", + "example": 0, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 0, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedGroup" } } } + } + }, + "tags": [ "" ] + } + } + }, + "components": { + "schemas": { + "PaginatedGroup": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "format": "int32", + "description": "The number of the current page." + } + } + } + } + } +}