From 4b4817f6007a870ff80b4c5e97faa22d70ac7070 Mon Sep 17 00:00:00 2001 From: PerthCharern Date: Thu, 30 Nov 2017 15:17:15 -0800 Subject: [PATCH 1/3] - Null object in collections is handled explicitly. Otherwise, operating an action on null (e.g. calling SerializeAsV3) has potential to yield null reference exception. - This is sort of a grey area since we will only encounter this for a "broken" object (e.g. a null schema in the Schemas map in Components - see the written unit tests), but I think it's a good idea to have this safeguard. Getting a generic null reference exception is a bad experience for our customer in any situation, I believe. - Unit test for Components --- .../Writers/OpenApiWriterExtensions.cs | 18 +- .../Models/OpenApiComponentsTests.cs | 233 +++++++++++++++++- 2 files changed, 247 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs index 3598f5eea..42ed2ff0c 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs @@ -302,7 +302,14 @@ private static void WriteCollectionInternal( { foreach (var item in elements) { - action(writer, item); + if (item != null) + { + action(writer, item); + } + else + { + writer.WriteNull(); + } } } @@ -325,7 +332,14 @@ private static void WriteMapInternal( foreach (var item in elements) { writer.WritePropertyName(item.Key); - action(writer, item.Value); + if (item.Value != null) + { + action(writer, item.Value); + } + else + { + writer.WriteNull(); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs index 3ded738e3..984a5eabd 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs @@ -3,12 +3,241 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System; +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Xunit; +using Xunit.Abstractions; namespace Microsoft.OpenApi.Tests.Models { public class OpenApiComponentsTests { - public static OpenApiComponents basicComponents = new OpenApiComponents(); + public static OpenApiComponents AdvancedComponents = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schema1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property3"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + } + }, + }, + }, + SecuritySchemes = new Dictionary + { + ["securityScheme1"] = new OpenApiSecurityScheme + { + Description = "description1", + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows + { + Implicit = new OpenApiOAuthFlow + { + Scopes = new Dictionary + { + ["operation1:object1"] = "operation 1 on object 1", + ["operation2:object2"] = "operation 2 on object 2" + }, + AuthorizationUrl = new Uri("https://example.com/api/oauth") + } + } + }, + ["securityScheme2"] = new OpenApiSecurityScheme + { + Description = "description1", + Type = SecuritySchemeType.OpenIdConnect, + Scheme = "openIdConnectUrl", + OpenIdConnectUrl = new Uri("https://example.com/openIdConnect") + } + } + }; + + public static OpenApiComponents BasicComponents = new OpenApiComponents(); + + public static OpenApiComponents BrokenComponents = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schema1"] = new OpenApiSchema + { + Type = "string" + }, + ["schema2"] = null + } + }; + + private readonly ITestOutputHelper _output; + + public OpenApiComponentsTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void SerializeBasicComponentsAsJsonWorks() + { + // Arrange + var expected = @"{ }"; + + // Act + var actual = BasicComponents.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeBasicComponentsAsYamlWorks() + { + // Arrange + var expected = @"{ }"; + + // Act + var actual = BasicComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeAdvancedComponentsAsJsonWorks() + { + // Arrange + var expected = @"{ + ""schemas"": { + ""schema1"": { + ""properties"": { + ""property2"": { + ""type"": ""integer"" + }, + ""property3"": { + ""maxLength"": 15, + ""type"": ""string"" + } + } + } + }, + ""securitySchemes"": { + ""securityScheme1"": { + ""type"": ""oauth2"", + ""description"": ""description1"", + ""flows"": { + ""implicit"": { + ""authorizationUrl"": ""https://example.com/api/oauth"", + ""scopes"": { + ""operation1:object1"": ""operation 1 on object 1"", + ""operation2:object2"": ""operation 2 on object 2"" + } + } + } + }, + ""securityScheme2"": { + ""type"": ""openIdConnect"", + ""description"": ""description1"", + ""openIdConnectUrl"": ""https://example.com/openIdConnect"" + } + } +}"; + + // Act + var actual = AdvancedComponents.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeAdvancedComponentsAsYamlWorks() + { + // Arrange + var expected = @"schemas: + schema1: + properties: + property2: + type: integer + property3: + maxLength: 15 + type: string +securitySchemes: + securityScheme1: + type: oauth2 + description: description1 + flows: + implicit: + authorizationUrl: https://example.com/api/oauth + scopes: + operation1:object1: operation 1 on object 1 + operation2:object2: operation 2 on object 2 + securityScheme2: + type: openIdConnect + description: description1 + openIdConnectUrl: https://example.com/openIdConnect"; + + // Act + var actual = AdvancedComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeBrokenComponentsAsJsonWorks() + { + // Arrange + var expected = @"{ + ""schemas"": { + ""schema1"": { + ""type"": ""string"" + }, + ""schema2"": null + } +}"; + + // Act + var actual = BrokenComponents.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeBrokenComponentsAsYamlWorks() + { + // Arrange + var expected = @"schemas: + schema1: + type: string + schema2: "; + + // Act + var actual = BrokenComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } } -} +} \ No newline at end of file From 3d9690770f8416f5b2c97ce21bb63bd4818a98e2 Mon Sep 17 00:00:00 2001 From: PerthCharern Date: Thu, 30 Nov 2017 15:17:49 -0800 Subject: [PATCH 2/3] Increase version number --- src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj | 2 +- src/Microsoft.OpenApi/Microsoft.OpenApi.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 34f5a3491..2fe7cb4cf 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -6,7 +6,7 @@ Microsoft Microsoft.OpenApi.Readers Microsoft.OpenApi.Readers - 1.0.0-beta006 + 1.0.0-beta007 OpenAPI.NET Readers for JSON and YAML documents Microsoft.OpenApi.Readers Microsoft.OpenApi.Readers diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index bae43cf74..6545cf63c 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -6,7 +6,7 @@ Microsoft Microsoft.OpenApi Microsoft.OpenApi - 1.0.0-beta006 + 1.0.0-beta007 .NET models and JSON/YAML writers for OpenAPI specification Microsoft.OpenApi Microsoft.OpenApi From bb19d9745136e1af621a69656e4e324daeb14ce2 Mon Sep 17 00:00:00 2001 From: PerthCharern Date: Thu, 30 Nov 2017 15:31:19 -0800 Subject: [PATCH 3/3] Make the unit test slightly more complicated --- .../Writers/OpenApiJsonWriter.cs | 2 + .../Models/OpenApiComponentsTests.cs | 44 +++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs index d0425caf4..b7f6f3b99 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs @@ -178,6 +178,8 @@ public override void WriteValue(string value) /// public override void WriteNull() { + WriteValueSeparator(); + Writer.Write("null"); } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs index 984a5eabd..5cb8345c4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs @@ -74,7 +74,23 @@ public class OpenApiComponentsTests { Type = "string" }, - ["schema2"] = null + ["schema2"] = null, + ["schema3"] = null, + ["schema4"] = new OpenApiSchema + { + Type = "string", + AllOf = new List() + { + null, + null, + new OpenApiSchema() + { + Type = "string" + }, + null, + null + } + } } }; @@ -209,7 +225,20 @@ public void SerializeBrokenComponentsAsJsonWorks() ""schema1"": { ""type"": ""string"" }, - ""schema2"": null + ""schema2"": null, + ""schema3"": null, + ""schema4"": { + ""type"": ""string"", + ""allOf"": [ + null, + null, + { + ""type"": ""string"" + }, + null, + null + ] + } } }"; @@ -229,7 +258,16 @@ public void SerializeBrokenComponentsAsYamlWorks() var expected = @"schemas: schema1: type: string - schema2: "; + schema2: + schema3: + schema4: + type: string + allOf: + - + - + - type: string + - + - "; // Act var actual = BrokenComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0_0);