From 0636e4a7e60f1bd8244333b756fb0a85a428e847 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 17 Aug 2022 13:18:26 +0300 Subject: [PATCH 01/15] Add a hashCode field to the diagnostics object to keep track of the hash value --- src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs | 5 +++++ src/Microsoft.OpenApi.Readers/ParsingContext.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs index c3178ccfb..937a13891 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs @@ -26,5 +26,10 @@ public class OpenApiDiagnostic : IDiagnostic /// Open API specification version of the document parsed. /// public OpenApiSpecVersion SpecificationVersion { get; set; } + + /// + /// The unique hash code of the generated OpenAPI document + /// + public int HashCode { get; set; } } } diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/ParsingContext.cs index 6c4dece2f..537a981b8 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/ParsingContext.cs @@ -75,6 +75,7 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument) throw new OpenApiUnsupportedSpecVersionException(inputVersion); } + Diagnostic.HashCode = doc.GetHashCode(); return doc; } From 500f0c7f45a81a13d51cafc9a74208d0280585a0 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 17 Aug 2022 13:19:21 +0300 Subject: [PATCH 02/15] Override the base GetHashCode() to compute the hash value for an OpenApi document and its property values --- .../Models/OpenApiDocument.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 44cbc71ab..9058dc7ba 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -375,6 +375,27 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return ResolveReference(reference, false); } + /// + /// Computes the hash code for an OpenApiDocument and its property values. + /// + /// The hash code. + public override int GetHashCode() + { + // select two random prime numbers e.g 1 and 3 and use them to compute hash codes + int hash = 1; + hash = hash * 3 + (Workspace == null ? 0 : Workspace.GetHashCode()); + hash = hash * 3 + (Info == null ? 0 : Info.GetHashCode()); + hash = hash * 3 + (Servers == null ? 0 : Servers.GetHashCode()); + hash = hash * 3 + (Paths == null ? 0 : Paths.GetHashCode()); + hash = hash * 3 + (Components == null ? 0 : Components.GetHashCode()); + hash = hash * 3 + (SecurityRequirements == null ? 0 : SecurityRequirements.GetHashCode()); + hash = hash * 3 + (Tags == null ? 0 : Tags.GetHashCode()); + hash = hash * 3 + (ExternalDocs == null ? 0 : ExternalDocs.GetHashCode()); + hash = hash * 3 + (Extensions == null ? 0 : Extensions.GetHashCode()); + + return hash; + } + /// /// Load the referenced object from a object /// From 7a58221b10c43bb7f42a01f679b12c46c5866cc9 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 17 Aug 2022 13:20:44 +0300 Subject: [PATCH 03/15] Simplify using statement, add tests, test file and cleanup --- .../OpenApiYamlDocumentReader.cs | 1 - .../Microsoft.OpenApi.Readers.Tests.csproj | 5 +- .../V3Tests/OpenApiDocumentTests.cs | 59 ++++++++++++++----- .../minimalDocumentWithWhitespace.yaml | 9 +++ 4 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs index aae09ec86..3aedafbf1 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs @@ -79,7 +79,6 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic { diagnostic.Warnings.Add(item); } - } return document; 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 e89e47745..98f228850 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 @@ - + net6.0 false @@ -137,6 +137,9 @@ Never + + Never + Never diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index f1d8b805f..b77fe8537 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -243,26 +243,55 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() [Fact] public void ParseMinimalDocumentShouldSucceed() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml"))) - { - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument + openApiDoc.Should().BeEquivalentTo( + new OpenApiDocument + { + Info = new OpenApiInfo { - Info = new OpenApiInfo - { - Title = "Simple Document", - Version = "0.9.1" - }, - Paths = new OpenApiPaths() - }); + Title = "Simple Document", + Version = "0.9.1" + }, + Paths = new OpenApiPaths() + }); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - } + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } + [Fact] + public void TestHashCodesForSimilarOpenApiDocuments() + { + // Arrange + using var stream1 = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); + using var stream2 = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); + using var stream3 = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocumentWithWhitespace.yaml")); + + // Act + /* + Test whether reading in the same document twice yields the same hash code, + And reading in similar documents but one has a whitespace yields the same hash code + */ + var openApiDoc1 = new OpenApiStreamReader().Read(stream1, out var diagnostic1); + var openApiDoc2 = new OpenApiStreamReader().Read(stream2, out var diagnostic2); + var openApiDoc3 = new OpenApiStreamReader().Read(stream3, out var diagnostic3); + + // Assert + /* The assumption is, if doc1.Equals(doc2), then doc1.GetHashCode().Equals(doc2.GetHashCode())*/ + if (openApiDoc1.Equals(openApiDoc2) && openApiDoc2.Equals(openApiDoc3)) + { + Assert.Equal(diagnostic1.HashCode, diagnostic2.HashCode); + Assert.Equal(diagnostic2.HashCode, diagnostic3.HashCode); + } + + /*Adding a server object to the original doc to check whether the hash code changes*/ + openApiDoc1.Servers = new List(); + var hash = openApiDoc1.GetHashCode(); + Assert.NotEqual(diagnostic1.HashCode, hash); + } + [Fact] public void ParseStandardPetStoreDocumentShouldSucceed() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml new file mode 100644 index 000000000..a68eb2fee --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml @@ -0,0 +1,9 @@ +openapi : 3.0.0 +info: + title: Simple Document + version: 0.9.1 + +paths: {} + + + From f9a32fa9233a982d690520d6cf2a4d5319fded9e Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 17 Aug 2022 14:24:12 +0300 Subject: [PATCH 04/15] Refactor failing tests --- .../V2Tests/OpenApiDocumentTests.cs | 4 +- .../V2Tests/OpenApiServerTests.cs | 12 +- .../V3Tests/OpenApiCallbackTests.cs | 155 +++++++++--------- .../V3Tests/OpenApiDocumentTests.cs | 101 ++++++------ .../V3Tests/OpenApiSchemaTests.cs | 15 +- 5 files changed, 141 insertions(+), 146 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 39bc0db80..b3f3033ac 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -150,9 +150,9 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) }, Paths = new OpenApiPaths() }); + var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }; - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); + Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); } [Fact] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs index c87b491ab..3e2cde88d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -280,14 +280,12 @@ public void InvalidHostShouldYieldError() var doc = reader.Read(input, out var diagnostic); doc.Servers.Count.Should().Be(0); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic + + Assert.Equal(OpenApiSpecVersion.OpenApi2_0, diagnostic.SpecificationVersion); + diagnostic.Errors.Should().BeEquivalentTo( + new List { - Errors = - { - new OpenApiError("#/", "Invalid host") - }, - SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 + new OpenApiError("#/", "Invalid host") }); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index 320f01fae..a89c087ef 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -21,29 +21,28 @@ public class OpenApiCallbackTests [Fact] public void ParseBasicCallbackShouldSucceed() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicCallback.yaml"))) - { - // Arrange - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicCallback.yaml")); + // Arrange + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var callback = OpenApiV3Deserializer.LoadCallback(node); + // Act + var callback = OpenApiV3Deserializer.LoadCallback(node); - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - callback.Should().BeEquivalentTo( - new OpenApiCallback + callback.Should().BeEquivalentTo( + new OpenApiCallback + { + PathItems = { - PathItems = - { [RuntimeExpression.Build("$request.body#/url")] = new OpenApiPathItem { @@ -69,33 +68,31 @@ public void ParseBasicCallbackShouldSucceed() } } } - } - }); - } + } + }); } [Fact] public void ParseCallbackWithReferenceShouldSucceed() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml"))) - { - // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml")); + // Act + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - // Assert - var path = openApiDoc.Paths.First().Value; - var subscribeOperation = path.Operations[OperationType.Post]; + // Assert + var path = openApiDoc.Paths.First().Value; + var subscribeOperation = path.Operations[OperationType.Post]; - var callback = subscribeOperation.Callbacks["simpleHook"]; + var callback = subscribeOperation.Callbacks["simpleHook"]; + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); - callback.Should().BeEquivalentTo( - new OpenApiCallback + callback.Should().BeEquivalentTo( + new OpenApiCallback + { + PathItems = { - PathItems = - { [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -122,39 +119,38 @@ public void ParseCallbackWithReferenceShouldSucceed() } } } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Callback, - Id = "simpleHook", - HostDocument = openApiDoc - } - }); - } + }, + Reference = new OpenApiReference + { + Type = ReferenceType.Callback, + Id = "simpleHook", + HostDocument = openApiDoc + } + }); } [Fact] public void ParseMultipleCallbacksWithReferenceShouldSucceed() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml"))) - { - // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml")); + // Act + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + + // Assert + var path = openApiDoc.Paths.First().Value; + var subscribeOperation = path.Operations[OperationType.Post]; - // Assert - var path = openApiDoc.Paths.First().Value; - var subscribeOperation = path.Operations[OperationType.Post]; + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); - var callback1 = subscribeOperation.Callbacks["simpleHook"]; + var callback1 = subscribeOperation.Callbacks["simpleHook"]; - callback1.Should().BeEquivalentTo( - new OpenApiCallback + callback1.Should().BeEquivalentTo( + new OpenApiCallback + { + PathItems = { - PathItems = - { [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -181,21 +177,21 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } } } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Callback, - Id = "simpleHook", - HostDocument = openApiDoc - } - }); + }, + Reference = new OpenApiReference + { + Type = ReferenceType.Callback, + Id = "simpleHook", + HostDocument = openApiDoc + } + }); - var callback2 = subscribeOperation.Callbacks["callback2"]; - callback2.Should().BeEquivalentTo( - new OpenApiCallback + var callback2 = subscribeOperation.Callbacks["callback2"]; + callback2.Should().BeEquivalentTo( + new OpenApiCallback + { + PathItems = { - PathItems = - { [RuntimeExpression.Build("/simplePath")]= new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -223,15 +219,15 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } }, } - } - }); + } + }); - var callback3 = subscribeOperation.Callbacks["callback3"]; - callback3.Should().BeEquivalentTo( - new OpenApiCallback + var callback3 = subscribeOperation.Callbacks["callback3"]; + callback3.Should().BeEquivalentTo( + new OpenApiCallback + { + PathItems = { - PathItems = - { [RuntimeExpression.Build(@"http://example.com?transactionId={$request.body#/id}&email={$request.body#/email}")] = new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -266,9 +262,8 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } } } - } - }); - } + } + }); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index b77fe8537..9ea3cfa4f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -99,8 +99,9 @@ public void ParseDocumentFromInlineStringShouldSucceed() Paths = new OpenApiPaths() }); - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); } [Theory] @@ -170,31 +171,30 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) }, Paths = new OpenApiPaths() }); + var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); } [Fact] public void ParseBasicDocumentWithMultipleServersShouldSucceed() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"))) - { - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml")); + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument + openApiDoc.Should().BeEquivalentTo( + new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "The API", + Version = "0.9.1", + }, + Servers = { - Info = new OpenApiInfo - { - Title = "The API", - Version = "0.9.1", - }, - Servers = - { new OpenApiServer { Url = new Uri("http://www.example.org/api").ToString(), @@ -205,39 +205,34 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() Url = new Uri("https://www.example.org/api").ToString(), Description = "The https endpoint" } - }, - Paths = new OpenApiPaths() - }); - } + }, + Paths = new OpenApiPaths() + }); } [Fact] public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml"))) - { - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml")); + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument + openApiDoc.Should().BeEquivalentTo( + new OpenApiDocument + { + Info = new OpenApiInfo { - Info = new OpenApiInfo - { - Version = "0.9" - }, - Paths = new OpenApiPaths() - }); + Version = "0.9" + }, + Paths = new OpenApiPaths() + }); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic - { - Errors = - { - new OpenApiValidatorError(nameof(OpenApiInfoRules.InfoRequiredFields),"#/info/title", "The field 'title' in 'info' object is REQUIRED.") - }, - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 - }); - } + diagnostic.Errors.Should().BeEquivalentTo( + new List + { + new OpenApiValidatorError(nameof(OpenApiInfoRules.InfoRequiredFields),"#/info/title", "The field 'title' in 'info' object is REQUIRED.") + }); + + Assert.Equal(OpenApiSpecVersion.OpenApi3_0, diagnostic.SpecificationVersion); } [Fact] @@ -257,8 +252,9 @@ public void ParseMinimalDocumentShouldSucceed() Paths = new OpenApiPaths() }); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); } [Fact] @@ -718,8 +714,9 @@ public void ParseStandardPetStoreDocumentShouldSucceed() actual.Should().BeEquivalentTo(expected); } - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); } [Fact] @@ -1251,8 +1248,9 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() actual.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument")); } - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); } [Fact] @@ -1267,8 +1265,9 @@ public void ParsePetStoreExpandedShouldSucceed() // TODO: Create the object in memory and compare with the one read from YAML file. } - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); } [Fact] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 9bdafeba6..63d7894c5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -332,8 +332,9 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() // Assert var components = openApiDoc.Components; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); components.Should().BeEquivalentTo( new OpenApiComponents @@ -438,8 +439,9 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() // Assert var components = openApiDoc.Components; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); components.Should().BeEquivalentTo( new OpenApiComponents @@ -619,8 +621,9 @@ public void ParseSelfReferencingSchemaShouldNotStackOverflow() // Assert var components = openApiDoc.Components; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + + Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); var schemaExtension = new OpenApiSchema() { From c7e3ed81e1bce61a19b2ca7fa236f162adeaf5b9 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Aug 2022 14:01:30 +0300 Subject: [PATCH 05/15] Compute hash value using hashing algorithm during serialization --- src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs | 2 +- .../OpenApiStreamReader.cs | 13 +++++++++++++ src/Microsoft.OpenApi.Readers/ParsingContext.cs | 1 - 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs index 937a13891..d634fe804 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs @@ -30,6 +30,6 @@ public class OpenApiDiagnostic : IDiagnostic /// /// The unique hash code of the generated OpenAPI document /// - public int HashCode { get; set; } + public string HashCode { get; set; } } } diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 13bdbdef8..d4dc709cf 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -3,6 +3,8 @@ using System; using System.IO; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -42,6 +44,17 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) { var reader = new StreamReader(input); var result = new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); + + HashAlgorithm sha = SHA512.Create(); + byte[] data = sha.ComputeHash(input); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.Length; i++) + { + sb.Append(data[i].ToString("X2")); + } + + diagnostic.HashCode = sb.ToString(); + if (!_settings.LeaveStreamOpen) { reader.Dispose(); diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/ParsingContext.cs index 537a981b8..6c4dece2f 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/ParsingContext.cs @@ -75,7 +75,6 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument) throw new OpenApiUnsupportedSpecVersionException(inputVersion); } - Diagnostic.HashCode = doc.GetHashCode(); return doc; } From 8d20075393b9c46e686580cead3b685c6d7027f2 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Aug 2022 14:21:51 +0300 Subject: [PATCH 06/15] Clean up test --- .../Models/OpenApiDocument.cs | 60 +++++++++++++------ .../V3Tests/OpenApiDocumentTests.cs | 15 +---- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 6ad301cc0..6b9d3af59 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -3,7 +3,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Services; @@ -62,6 +66,8 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible /// public IDictionary Extensions { get; set; } = new Dictionary(); + private static readonly object locker = new(); + /// /// Parameter-less constructor /// @@ -375,26 +381,42 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return ResolveReference(reference, false); } - /// - /// Computes the hash code for an OpenApiDocument and its property values. - /// - /// The hash code. - public override int GetHashCode() + ///// + ///// Computes the hash code for an OpenApiDocument and its property values. + ///// + ///// The hash code. + //public override int GetHashCode() + //{ + // // select two random prime numbers e.g 1 and 3 and use them to compute hash codes + // int hash = 1; + // hash = hash * 3 + (Workspace == null ? 0 : Workspace.GetHashCode()); + // hash = hash * 3 + (Info == null ? 0 : Info.GetHashCode()); + // hash = hash * 3 + (Servers == null ? 0 : Servers.GetHashCode()); + // hash = hash * 3 + (Paths == null ? 0 : Paths.GetHashCode()); + // hash = hash * 3 + (Components == null ? 0 : Components.GetHashCode()); + // hash = hash * 3 + (SecurityRequirements == null ? 0 : SecurityRequirements.GetHashCode()); + // hash = hash * 3 + (Tags == null ? 0 : Tags.GetHashCode()); + // hash = hash * 3 + (ExternalDocs == null ? 0 : ExternalDocs.GetHashCode()); + // hash = hash * 3 + (Extensions == null ? 0 : Extensions.GetHashCode()); + + // return hash; + //} + + public static string GenerateHashValue(Stream input) { - // select two random prime numbers e.g 1 and 3 and use them to compute hash codes - int hash = 1; - hash = hash * 3 + (Workspace == null ? 0 : Workspace.GetHashCode()); - hash = hash * 3 + (Info == null ? 0 : Info.GetHashCode()); - hash = hash * 3 + (Servers == null ? 0 : Servers.GetHashCode()); - hash = hash * 3 + (Paths == null ? 0 : Paths.GetHashCode()); - hash = hash * 3 + (Components == null ? 0 : Components.GetHashCode()); - hash = hash * 3 + (SecurityRequirements == null ? 0 : SecurityRequirements.GetHashCode()); - hash = hash * 3 + (Tags == null ? 0 : Tags.GetHashCode()); - hash = hash * 3 + (ExternalDocs == null ? 0 : ExternalDocs.GetHashCode()); - hash = hash * 3 + (Extensions == null ? 0 : Extensions.GetHashCode()); - - return hash; - } + HashAlgorithm sha = SHA512.Create(); + byte[] result = sha.ComputeHash(input); + + // Build the final string by converting each byte + // into hex and appending it to a StringBuilder + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < result.Length; i++) + { + sb.Append(result[i].ToString("X2")); + } + + return sb.ToString(); + } /// /// Load the referenced object from a object diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 9ea3cfa4f..242dc6b74 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -9,13 +9,11 @@ using System.Threading; using FluentAssertions; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Validations.Rules; using Microsoft.OpenApi.Writers; -using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; @@ -275,17 +273,8 @@ And reading in similar documents but one has a whitespace yields the same hash c var openApiDoc3 = new OpenApiStreamReader().Read(stream3, out var diagnostic3); // Assert - /* The assumption is, if doc1.Equals(doc2), then doc1.GetHashCode().Equals(doc2.GetHashCode())*/ - if (openApiDoc1.Equals(openApiDoc2) && openApiDoc2.Equals(openApiDoc3)) - { - Assert.Equal(diagnostic1.HashCode, diagnostic2.HashCode); - Assert.Equal(diagnostic2.HashCode, diagnostic3.HashCode); - } - - /*Adding a server object to the original doc to check whether the hash code changes*/ - openApiDoc1.Servers = new List(); - var hash = openApiDoc1.GetHashCode(); - Assert.NotEqual(diagnostic1.HashCode, hash); + Assert.Equal(diagnostic1.HashCode, diagnostic2.HashCode); + Assert.Equal(diagnostic2.HashCode, diagnostic3.HashCode); } [Fact] From a676e37ede211ea94fe521cd56373d28e2386c68 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Aug 2022 14:22:33 +0300 Subject: [PATCH 07/15] Code cleanup --- .../OpenApiStreamReader.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index d4dc709cf..0554108b2 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -45,15 +45,17 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) var reader = new StreamReader(input); var result = new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); - HashAlgorithm sha = SHA512.Create(); - byte[] data = sha.ComputeHash(input); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < data.Length; i++) - { - sb.Append(data[i].ToString("X2")); - } + //HashAlgorithm sha = SHA512.Create(); + //byte[] data = sha.ComputeHash(input); + //StringBuilder sb = new StringBuilder(); + //for (int i = 0; i < data.Length; i++) + //{ + // sb.Append(data[i].ToString("X2")); + //} + - diagnostic.HashCode = sb.ToString(); + //diagnostic.HashCode = sb.ToString(); + diagnostic.HashCode = OpenApiDocument.GenerateHashValue(input); if (!_settings.LeaveStreamOpen) { From 96f060bce9cea7867bdc3d8bcd014bdeae49b5dc Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Aug 2022 15:33:33 +0300 Subject: [PATCH 08/15] Code cleanup --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 80 +------------------ .../OpenApiStreamReader.cs | 10 --- .../Models/OpenApiDocument.cs | 29 ++----- 3 files changed, 6 insertions(+), 113 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index c37c9479d..461ca50be 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -356,57 +356,7 @@ public static OpenApiDocument FixReferences(OpenApiDocument document) return doc; } - - private static async Task GetStream(string input, ILogger logger) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - Stream stream; - if (input.StartsWith("http")) - { - try - { - var httpClientHandler = new HttpClientHandler() - { - SslProtocols = System.Security.Authentication.SslProtocols.Tls12, - }; - using var httpClient = new HttpClient(httpClientHandler) - { - DefaultRequestVersion = HttpVersion.Version20 - }; - stream = await httpClient.GetStreamAsync(input); - } - catch (HttpRequestException ex) - { - logger.LogError($"Could not download the file at {input}, reason{ex}"); - return null; - } - } - else - { - try - { - var fileInput = new FileInfo(input); - stream = fileInput.OpenRead(); - } - catch (Exception ex) when (ex is FileNotFoundException || - ex is PathTooLongException || - ex is DirectoryNotFoundException || - ex is IOException || - ex is UnauthorizedAccessException || - ex is SecurityException || - ex is NotSupportedException) - { - logger.LogError($"Could not open the file at {input}, reason: {ex.Message}"); - return null; - } - } - stopwatch.Stop(); - logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input); - return stream; - } - + /// /// Takes in a file stream, parses the stream into a JsonDocument and gets a list of paths and Http methods /// @@ -462,34 +412,6 @@ private static Dictionary> EnumerateJsonDocument(JsonElemen return paths; } - /// - /// Fixes the references in the resulting OpenApiDocument. - /// - /// The converted OpenApiDocument. - /// A valid OpenApiDocument instance. - // private static OpenApiDocument FixReferences2(OpenApiDocument document) - // { - // // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance. - // // So we write it out, and read it back in again to fix it up. - - // OpenApiDocument document; - // logger.LogTrace("Parsing the OpenApi file"); - // var result = await new OpenApiStreamReader(new OpenApiReaderSettings - // { - // RuleSet = ValidationRuleSet.GetDefaultRuleSet(), - // BaseUrl = new Uri(openapi) - // } - // ).ReadAsync(stream); - - // document = result.OpenApiDocument; - // var context = result.OpenApiDiagnostic; - // var sb = new StringBuilder(); - // document.SerializeAsV3(new OpenApiYamlWriter(new StringWriter(sb))); - // var doc = new OpenApiStringReader().Read(sb.ToString(), out _); - - // return doc; - // } - /// /// Reads stream from file system or makes HTTP request depending on the input string /// diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 0554108b2..6d21a692f 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -45,16 +45,6 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) var reader = new StreamReader(input); var result = new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); - //HashAlgorithm sha = SHA512.Create(); - //byte[] data = sha.ComputeHash(input); - //StringBuilder sb = new StringBuilder(); - //for (int i = 0; i < data.Length; i++) - //{ - // sb.Append(data[i].ToString("X2")); - //} - - - //diagnostic.HashCode = sb.ToString(); diagnostic.HashCode = OpenApiDocument.GenerateHashValue(input); if (!_settings.LeaveStreamOpen) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 6b9d3af59..ccdb65092 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; using System.Text; using Microsoft.OpenApi.Exceptions; @@ -66,8 +65,6 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible /// public IDictionary Extensions { get; set; } = new Dictionary(); - private static readonly object locker = new(); - /// /// Parameter-less constructor /// @@ -381,27 +378,11 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return ResolveReference(reference, false); } - ///// - ///// Computes the hash code for an OpenApiDocument and its property values. - ///// - ///// The hash code. - //public override int GetHashCode() - //{ - // // select two random prime numbers e.g 1 and 3 and use them to compute hash codes - // int hash = 1; - // hash = hash * 3 + (Workspace == null ? 0 : Workspace.GetHashCode()); - // hash = hash * 3 + (Info == null ? 0 : Info.GetHashCode()); - // hash = hash * 3 + (Servers == null ? 0 : Servers.GetHashCode()); - // hash = hash * 3 + (Paths == null ? 0 : Paths.GetHashCode()); - // hash = hash * 3 + (Components == null ? 0 : Components.GetHashCode()); - // hash = hash * 3 + (SecurityRequirements == null ? 0 : SecurityRequirements.GetHashCode()); - // hash = hash * 3 + (Tags == null ? 0 : Tags.GetHashCode()); - // hash = hash * 3 + (ExternalDocs == null ? 0 : ExternalDocs.GetHashCode()); - // hash = hash * 3 + (Extensions == null ? 0 : Extensions.GetHashCode()); - - // return hash; - //} - + /// + /// Uses the stream input to generate the hash value of an OpenApi document + /// + /// Stream containing OpenAPI description to hash. + /// The hash value. public static string GenerateHashValue(Stream input) { HashAlgorithm sha = SHA512.Create(); From 9772ad07a467e365f925b2fbcf5d921e9b1cb637 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Aug 2022 16:12:41 +0300 Subject: [PATCH 09/15] Update public Api interface --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index b320f8b10..11ed58e88 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1,7 +1,7 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Microsoft/OpenAPI.NET")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName="")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] namespace Microsoft.OpenApi.Any { public enum AnyType @@ -535,6 +535,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IEnumerable ResolveReferences() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public static string GenerateHashValue(System.IO.Stream input) { } } public class OpenApiEncoding : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { From d50f8577ccb2f2b54683a9c6026f73037f5905b1 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 22 Aug 2022 16:12:41 +0300 Subject: [PATCH 10/15] Revert "Update public Api interface" This reverts commit 9772ad07a467e365f925b2fbcf5d921e9b1cb637. --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 11ed58e88..b320f8b10 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1,7 +1,7 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Microsoft/OpenAPI.NET")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName="")] namespace Microsoft.OpenApi.Any { public enum AnyType @@ -535,7 +535,6 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IEnumerable ResolveReferences() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public static string GenerateHashValue(System.IO.Stream input) { } } public class OpenApiEncoding : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { From 8674fe332aa83d44c25427e3d047bc9d3fd9a2dd Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Fri, 26 Aug 2022 12:32:08 +0300 Subject: [PATCH 11/15] Revert previous changes --- .../OpenApiDiagnostic.cs | 5 - .../OpenApiStreamReader.cs | 2 - .../Microsoft.OpenApi.Readers.Tests.csproj | 3 - .../V2Tests/OpenApiDocumentTests.cs | 8 +- .../V2Tests/OpenApiServerTests.cs | 12 +- .../V3Tests/OpenApiCallbackTests.cs | 155 ++++++++--------- .../V3Tests/OpenApiDocumentTests.cs | 157 ++++++++---------- .../V3Tests/OpenApiSchemaTests.cs | 17 +- 8 files changed, 169 insertions(+), 190 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs index d634fe804..c3178ccfb 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs @@ -26,10 +26,5 @@ public class OpenApiDiagnostic : IDiagnostic /// Open API specification version of the document parsed. /// public OpenApiSpecVersion SpecificationVersion { get; set; } - - /// - /// The unique hash code of the generated OpenAPI document - /// - public string HashCode { get; set; } } } diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 6d21a692f..f6c9e7613 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -45,8 +45,6 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) var reader = new StreamReader(input); var result = new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); - diagnostic.HashCode = OpenApiDocument.GenerateHashValue(input); - if (!_settings.LeaveStreamOpen) { reader.Dispose(); 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 597302062..94432db9a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -137,9 +137,6 @@ Never - - Never - Never diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index b3f3033ac..fcf0471ea 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -150,9 +150,9 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) }, Paths = new OpenApiPaths() }); - var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }; - Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); } [Fact] @@ -208,7 +208,7 @@ public void ShouldParseProducesInAnyOrder() { Type = ReferenceType.Schema, Id = "Error", - HostDocument= doc + HostDocument = doc }, Properties = new Dictionary() { @@ -407,7 +407,7 @@ public void ShouldAssignSchemaToAllResponses() { Id = "Error", Type = ReferenceType.Schema, - HostDocument= document + HostDocument = document } }; var responses = document.Paths["/items"].Operations[OperationType.Get].Responses; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs index 3e2cde88d..c87b491ab 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -280,12 +280,14 @@ public void InvalidHostShouldYieldError() var doc = reader.Read(input, out var diagnostic); doc.Servers.Count.Should().Be(0); - - Assert.Equal(OpenApiSpecVersion.OpenApi2_0, diagnostic.SpecificationVersion); - diagnostic.Errors.Should().BeEquivalentTo( - new List + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic { - new OpenApiError("#/", "Invalid host") + Errors = + { + new OpenApiError("#/", "Invalid host") + }, + SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index a89c087ef..320f01fae 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -21,28 +21,29 @@ public class OpenApiCallbackTests [Fact] public void ParseBasicCallbackShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicCallback.yaml")); - // Arrange - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicCallback.yaml"))) + { + // Arrange + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var callback = OpenApiV3Deserializer.LoadCallback(node); + // Act + var callback = OpenApiV3Deserializer.LoadCallback(node); - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - callback.Should().BeEquivalentTo( - new OpenApiCallback - { - PathItems = + callback.Should().BeEquivalentTo( + new OpenApiCallback { + PathItems = + { [RuntimeExpression.Build("$request.body#/url")] = new OpenApiPathItem { @@ -68,31 +69,33 @@ public void ParseBasicCallbackShouldSucceed() } } } - } - }); + } + }); + } } [Fact] public void ParseCallbackWithReferenceShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml")); - // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml"))) + { + // Act + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - // Assert - var path = openApiDoc.Paths.First().Value; - var subscribeOperation = path.Operations[OperationType.Post]; + // Assert + var path = openApiDoc.Paths.First().Value; + var subscribeOperation = path.Operations[OperationType.Post]; - var callback = subscribeOperation.Callbacks["simpleHook"]; - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + var callback = subscribeOperation.Callbacks["simpleHook"]; - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - callback.Should().BeEquivalentTo( - new OpenApiCallback - { - PathItems = + callback.Should().BeEquivalentTo( + new OpenApiCallback { + PathItems = + { [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -119,38 +122,39 @@ public void ParseCallbackWithReferenceShouldSucceed() } } } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Callback, - Id = "simpleHook", - HostDocument = openApiDoc - } - }); + }, + Reference = new OpenApiReference + { + Type = ReferenceType.Callback, + Id = "simpleHook", + HostDocument = openApiDoc + } + }); + } } [Fact] public void ParseMultipleCallbacksWithReferenceShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml")); - // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - - // Assert - var path = openApiDoc.Paths.First().Value; - var subscribeOperation = path.Operations[OperationType.Post]; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml"))) + { + // Act + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + // Assert + var path = openApiDoc.Paths.First().Value; + var subscribeOperation = path.Operations[OperationType.Post]; - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - var callback1 = subscribeOperation.Callbacks["simpleHook"]; + var callback1 = subscribeOperation.Callbacks["simpleHook"]; - callback1.Should().BeEquivalentTo( - new OpenApiCallback - { - PathItems = + callback1.Should().BeEquivalentTo( + new OpenApiCallback { + PathItems = + { [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -177,21 +181,21 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } } } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Callback, - Id = "simpleHook", - HostDocument = openApiDoc - } - }); + }, + Reference = new OpenApiReference + { + Type = ReferenceType.Callback, + Id = "simpleHook", + HostDocument = openApiDoc + } + }); - var callback2 = subscribeOperation.Callbacks["callback2"]; - callback2.Should().BeEquivalentTo( - new OpenApiCallback - { - PathItems = + var callback2 = subscribeOperation.Callbacks["callback2"]; + callback2.Should().BeEquivalentTo( + new OpenApiCallback { + PathItems = + { [RuntimeExpression.Build("/simplePath")]= new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -219,15 +223,15 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } }, } - } - }); + } + }); - var callback3 = subscribeOperation.Callbacks["callback3"]; - callback3.Should().BeEquivalentTo( - new OpenApiCallback - { - PathItems = + var callback3 = subscribeOperation.Callbacks["callback3"]; + callback3.Should().BeEquivalentTo( + new OpenApiCallback { + PathItems = + { [RuntimeExpression.Build(@"http://example.com?transactionId={$request.body#/id}&email={$request.body#/email}")] = new OpenApiPathItem { Operations = { [OperationType.Post] = new OpenApiOperation() @@ -262,8 +266,9 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } } } - } - }); + } + }); + } } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 242dc6b74..6fbb7065a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -9,11 +9,13 @@ using System.Threading; using FluentAssertions; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Validations.Rules; using Microsoft.OpenApi.Writers; +using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; @@ -32,8 +34,10 @@ public T Clone(T element) where T : IOpenApiSerializable { IOpenApiWriter writer; var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); - writer = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings() { - InlineLocalReferences = true}); + writer = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings() + { + InlineLocalReferences = true + }); element.SerializeAsV3(writer); writer.Flush(); stream.Position = 0; @@ -46,7 +50,7 @@ public T Clone(T element) where T : IOpenApiSerializable } } - public OpenApiSecurityScheme CloneSecurityScheme(OpenApiSecurityScheme element) + public OpenApiSecurityScheme CloneSecurityScheme(OpenApiSecurityScheme element) { using (var stream = new MemoryStream()) { @@ -97,9 +101,8 @@ public void ParseDocumentFromInlineStringShouldSucceed() Paths = new OpenApiPaths() }); - var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Theory] @@ -169,30 +172,31 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) }, Paths = new OpenApiPaths() }); - var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] public void ParseBasicDocumentWithMultipleServersShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"))) + { + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument - { - Info = new OpenApiInfo - { - Title = "The API", - Version = "0.9.1", - }, - Servers = + openApiDoc.Should().BeEquivalentTo( + new OpenApiDocument { + Info = new OpenApiInfo + { + Title = "The API", + Version = "0.9.1", + }, + Servers = + { new OpenApiServer { Url = new Uri("http://www.example.org/api").ToString(), @@ -203,80 +207,64 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() Url = new Uri("https://www.example.org/api").ToString(), Description = "The https endpoint" } - }, - Paths = new OpenApiPaths() - }); + }, + Paths = new OpenApiPaths() + }); + } } [Fact] public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml"))) + { + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument - { - Info = new OpenApiInfo + openApiDoc.Should().BeEquivalentTo( + new OpenApiDocument { - Version = "0.9" - }, - Paths = new OpenApiPaths() - }); - - diagnostic.Errors.Should().BeEquivalentTo( - new List - { - new OpenApiValidatorError(nameof(OpenApiInfoRules.InfoRequiredFields),"#/info/title", "The field 'title' in 'info' object is REQUIRED.") - }); + Info = new OpenApiInfo + { + Version = "0.9" + }, + Paths = new OpenApiPaths() + }); - Assert.Equal(OpenApiSpecVersion.OpenApi3_0, diagnostic.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic + { + Errors = + { + new OpenApiValidatorError(nameof(OpenApiInfoRules.InfoRequiredFields),"#/info/title", "The field 'title' in 'info' object is REQUIRED.") + }, + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 + }); + } } [Fact] public void ParseMinimalDocumentShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml"))) + { + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument - { - Info = new OpenApiInfo + openApiDoc.Should().BeEquivalentTo( + new OpenApiDocument { - Title = "Simple Document", - Version = "0.9.1" - }, - Paths = new OpenApiPaths() - }); - - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; + Info = new OpenApiInfo + { + Title = "Simple Document", + Version = "0.9.1" + }, + Paths = new OpenApiPaths() + }); - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + } } - [Fact] - public void TestHashCodesForSimilarOpenApiDocuments() - { - // Arrange - using var stream1 = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); - using var stream2 = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); - using var stream3 = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocumentWithWhitespace.yaml")); - - // Act - /* - Test whether reading in the same document twice yields the same hash code, - And reading in similar documents but one has a whitespace yields the same hash code - */ - var openApiDoc1 = new OpenApiStreamReader().Read(stream1, out var diagnostic1); - var openApiDoc2 = new OpenApiStreamReader().Read(stream2, out var diagnostic2); - var openApiDoc3 = new OpenApiStreamReader().Read(stream3, out var diagnostic3); - - // Assert - Assert.Equal(diagnostic1.HashCode, diagnostic2.HashCode); - Assert.Equal(diagnostic2.HashCode, diagnostic3.HashCode); - } - [Fact] public void ParseStandardPetStoreDocumentShouldSucceed() { @@ -703,9 +691,8 @@ public void ParseStandardPetStoreDocumentShouldSucceed() actual.Should().BeEquivalentTo(expected); } - var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] @@ -1237,9 +1224,8 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() actual.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument")); } - var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] @@ -1254,9 +1240,8 @@ public void ParsePetStoreExpandedShouldSucceed() // TODO: Create the object in memory and compare with the one read from YAML file. } - var context2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(context.SpecificationVersion, context2.SpecificationVersion); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 63d7894c5..0101d9c6e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -332,9 +332,8 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() // Assert var components = openApiDoc.Components; - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); components.Should().BeEquivalentTo( new OpenApiComponents @@ -424,7 +423,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() } } } - },options => options.Excluding(m => m.Name == "HostDocument")); + }, options => options.Excluding(m => m.Name == "HostDocument")); } } @@ -439,9 +438,8 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() // Assert var components = openApiDoc.Components; - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); components.Should().BeEquivalentTo( new OpenApiComponents @@ -621,9 +619,8 @@ public void ParseSelfReferencingSchemaShouldNotStackOverflow() // Assert var components = openApiDoc.Components; - var diagnostic2 = new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }; - - Assert.Equal(diagnostic.SpecificationVersion, diagnostic2.SpecificationVersion); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); var schemaExtension = new OpenApiSchema() { From ba7377748d9560b6fd8160ebfbb0bd66ed6d3db2 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Fri, 26 Aug 2022 12:33:05 +0300 Subject: [PATCH 12/15] Refactor hashing logic and add test cases --- .../Models/OpenApiDocument.cs | 33 +++++++++++++++---- .../Microsoft.OpenApi.Tests.csproj | 14 ++++++++ .../Models/OpenApiDocumentTests.cs | 30 ++++++++++++++++- .../Models/Samples/sampleDocument.yaml | 5 +++ .../sampleDocumentWithWhiteSpaces.yaml} | 6 ++-- 5 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocument.yaml rename test/{Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml => Microsoft.OpenApi.Tests/Models/Samples/sampleDocumentWithWhiteSpaces.yaml} (100%) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index ccdb65092..09a183811 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.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; @@ -65,6 +65,11 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible /// public IDictionary Extensions { get; set; } = new Dictionary(); + /// + /// The unique hash code of the generated OpenAPI document + /// + public string HashCode => GenerateHashValue(this); + /// /// Parameter-less constructor /// @@ -379,21 +384,35 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) } /// - /// Uses the stream input to generate the hash value of an OpenApi document + /// Takes in an OpenApi document instance and generates its hash value /// - /// Stream containing OpenAPI description to hash. + /// The OpenAPI description to hash. /// The hash value. - public static string GenerateHashValue(Stream input) + public static string GenerateHashValue(OpenApiDocument doc) { HashAlgorithm sha = SHA512.Create(); - byte[] result = sha.ComputeHash(input); + using var memoryStream = new MemoryStream(); + + using var cryptoStream = new CryptoStream(memoryStream, sha, CryptoStreamMode.Write); + using var streamWriter = new StreamWriter(cryptoStream); + + var openApiJsonWriter = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings { Terse = true }); + doc.SerializeAsV3(openApiJsonWriter); + openApiJsonWriter.Flush(); + var hash = memoryStream.ToArray(); + + return ConvertByteArrayToString(hash); + } + + private static string ConvertByteArrayToString(byte[] hash) + { // Build the final string by converting each byte // into hex and appending it to a StringBuilder StringBuilder sb = new StringBuilder(); - for (int i = 0; i < result.Length; i++) + for (int i = 0; i < hash.Length; i++) { - sb.Append(result[i].ToString("X2")); + sb.Append(hash[i].ToString("X2")); } return sb.ToString(); diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index a6ba76259..872447cc9 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -36,6 +36,20 @@ + + Always + + + Always + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 10cadd597..cd4cc2b5a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.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; @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -1314,5 +1315,32 @@ public void SerializeRelativeRootPathWithHostAsV2JsonWorks() actual.Should().Be(expected); } + [Fact] + public void TestHashCodesForSimilarOpenApiDocuments() + { + // Arrange + var sampleFolderPath = "Models/Samples/"; + + var doc1 = ParseInputFile(Path.Combine(sampleFolderPath, "sampleDocument.yaml")); + var doc2 = ParseInputFile(Path.Combine(sampleFolderPath, "sampleDocument.yaml")); + var doc3 = ParseInputFile(Path.Combine(sampleFolderPath, "sampleDocumentWithWhiteSpaces.yaml")); + + // Act && Assert + /* + Test whether reading in two similar documents yield the same hash code, + And reading in similar documents(one has a whitespace) yields the same hash code as the result is terse + */ + Assert.True(doc1.HashCode != null && doc2.HashCode != null && doc1.HashCode.Equals(doc2.HashCode)); + Assert.Equal(doc1.HashCode, doc3.HashCode); + } + + private static OpenApiDocument ParseInputFile(string filePath) + { + // Read in the input yaml file + using FileStream stream = File.OpenRead(filePath); + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + + return openApiDoc; + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocument.yaml b/test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocument.yaml new file mode 100644 index 000000000..34153a5f5 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocument.yaml @@ -0,0 +1,5 @@ +openapi : 3.0.0 +info: + title: Simple Document + version: 0.9.1 +paths: {} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml b/test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocumentWithWhiteSpaces.yaml similarity index 100% rename from test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml rename to test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocumentWithWhiteSpaces.yaml index a68eb2fee..5f31baa0e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minimalDocumentWithWhitespace.yaml +++ b/test/Microsoft.OpenApi.Tests/Models/Samples/sampleDocumentWithWhiteSpaces.yaml @@ -1,9 +1,9 @@ openapi : 3.0.0 + info: title: Simple Document - version: 0.9.1 -paths: {} - + version: 0.9.1 +paths: {} From 2bb383cd0630fd86967fb08aa48238a7c6ecd59a Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Fri, 26 Aug 2022 12:54:26 +0300 Subject: [PATCH 13/15] Update public API interface --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index b320f8b10..823ddab81 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1,7 +1,7 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Microsoft/OpenAPI.NET")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName="")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] namespace Microsoft.OpenApi.Any { public enum AnyType @@ -525,6 +525,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiComponents Components { get; set; } public System.Collections.Generic.IDictionary Extensions { get; set; } public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } + public string HashCode { get; } public Microsoft.OpenApi.Models.OpenApiInfo Info { get; set; } public Microsoft.OpenApi.Models.OpenApiPaths Paths { get; set; } public System.Collections.Generic.IList SecurityRequirements { get; set; } @@ -535,6 +536,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IEnumerable ResolveReferences() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public static string GenerateHashValue(Microsoft.OpenApi.Models.OpenApiDocument doc) { } } public class OpenApiEncoding : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { From feebffc73295355c80588e4d805a20fb4d1ce5ff Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Fri, 26 Aug 2022 13:04:57 +0300 Subject: [PATCH 14/15] Remove framework display name from interface --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 823ddab81..745d91d43 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1,7 +1,7 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Microsoft/OpenAPI.NET")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName="")] namespace Microsoft.OpenApi.Any { public enum AnyType From 25395760922737cef9efd8c54f55af26bb16ca67 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Fri, 26 Aug 2022 16:28:10 +0300 Subject: [PATCH 15/15] Address PR feedback --- src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs | 3 --- src/Microsoft.OpenApi/Models/OpenApiDocument.cs | 9 ++++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index f6c9e7613..13bdbdef8 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -3,8 +3,6 @@ using System; using System.IO; -using System.Security.Cryptography; -using System.Text; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -44,7 +42,6 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) { var reader = new StreamReader(input); var result = new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); - if (!_settings.LeaveStreamOpen) { reader.Dispose(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 09a183811..836e45dd8 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -390,17 +390,16 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) /// The hash value. public static string GenerateHashValue(OpenApiDocument doc) { - HashAlgorithm sha = SHA512.Create(); - using var memoryStream = new MemoryStream(); - - using var cryptoStream = new CryptoStream(memoryStream, sha, CryptoStreamMode.Write); + using HashAlgorithm sha = SHA512.Create(); + using var cryptoStream = new CryptoStream(Stream.Null, sha, CryptoStreamMode.Write); using var streamWriter = new StreamWriter(cryptoStream); var openApiJsonWriter = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings { Terse = true }); doc.SerializeAsV3(openApiJsonWriter); openApiJsonWriter.Flush(); - var hash = memoryStream.ToArray(); + cryptoStream.FlushFinalBlock(); + var hash = sha.Hash; return ConvertByteArrayToString(hash); }