Skip to content

Commit ba73777

Browse files
committed
Refactor hashing logic and add test cases
1 parent 8674fe3 commit ba73777

File tree

5 files changed

+77
-11
lines changed

5 files changed

+77
-11
lines changed

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

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

44
using System;
@@ -65,6 +65,11 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
6565
/// </summary>
6666
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
6767

68+
/// <summary>
69+
/// The unique hash code of the generated OpenAPI document
70+
/// </summary>
71+
public string HashCode => GenerateHashValue(this);
72+
6873
/// <summary>
6974
/// Parameter-less constructor
7075
/// </summary>
@@ -379,21 +384,35 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
379384
}
380385

381386
/// <summary>
382-
/// Uses the stream input to generate the hash value of an OpenApi document
387+
/// Takes in an OpenApi document instance and generates its hash value
383388
/// </summary>
384-
/// <param name="input">Stream containing OpenAPI description to hash.</param>
389+
/// <param name="doc">The OpenAPI description to hash.</param>
385390
/// <returns>The hash value.</returns>
386-
public static string GenerateHashValue(Stream input)
391+
public static string GenerateHashValue(OpenApiDocument doc)
387392
{
388393
HashAlgorithm sha = SHA512.Create();
389-
byte[] result = sha.ComputeHash(input);
394+
using var memoryStream = new MemoryStream();
395+
396+
using var cryptoStream = new CryptoStream(memoryStream, sha, CryptoStreamMode.Write);
397+
using var streamWriter = new StreamWriter(cryptoStream);
398+
399+
var openApiJsonWriter = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings { Terse = true });
400+
doc.SerializeAsV3(openApiJsonWriter);
401+
openApiJsonWriter.Flush();
390402

403+
var hash = memoryStream.ToArray();
404+
405+
return ConvertByteArrayToString(hash);
406+
}
407+
408+
private static string ConvertByteArrayToString(byte[] hash)
409+
{
391410
// Build the final string by converting each byte
392411
// into hex and appending it to a StringBuilder
393412
StringBuilder sb = new StringBuilder();
394-
for (int i = 0; i < result.Length; i++)
413+
for (int i = 0; i < hash.Length; i++)
395414
{
396-
sb.Append(result[i].ToString("X2"));
415+
sb.Append(hash[i].ToString("X2"));
397416
}
398417

399418
return sb.ToString();

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@
3636
</ItemGroup>
3737

3838
<ItemGroup>
39+
<EmbeddedResource Include="Models\Samples\sampleDocument.yaml">
40+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
41+
</EmbeddedResource>
42+
<EmbeddedResource Include="Models\Samples\sampleDocumentWithWhiteSpaces.yaml">
43+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
44+
</EmbeddedResource>
45+
</ItemGroup>
46+
47+
<ItemGroup>
48+
3949
<None Update="PublicApi\PublicApi.approved.txt" CopyToOutputDirectory="Always" />
4050
</ItemGroup>
51+
52+
<ItemGroup>
53+
<Folder Include="Models\Samples\" />
54+
</ItemGroup>
4155
</Project>

test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs

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

44
using System;
@@ -10,6 +10,7 @@
1010
using Microsoft.OpenApi.Extensions;
1111
using Microsoft.OpenApi.Interfaces;
1212
using Microsoft.OpenApi.Models;
13+
using Microsoft.OpenApi.Readers;
1314
using Microsoft.OpenApi.Writers;
1415
using VerifyXunit;
1516
using Xunit;
@@ -1314,5 +1315,32 @@ public void SerializeRelativeRootPathWithHostAsV2JsonWorks()
13141315
actual.Should().Be(expected);
13151316
}
13161317

1318+
[Fact]
1319+
public void TestHashCodesForSimilarOpenApiDocuments()
1320+
{
1321+
// Arrange
1322+
var sampleFolderPath = "Models/Samples/";
1323+
1324+
var doc1 = ParseInputFile(Path.Combine(sampleFolderPath, "sampleDocument.yaml"));
1325+
var doc2 = ParseInputFile(Path.Combine(sampleFolderPath, "sampleDocument.yaml"));
1326+
var doc3 = ParseInputFile(Path.Combine(sampleFolderPath, "sampleDocumentWithWhiteSpaces.yaml"));
1327+
1328+
// Act && Assert
1329+
/*
1330+
Test whether reading in two similar documents yield the same hash code,
1331+
And reading in similar documents(one has a whitespace) yields the same hash code as the result is terse
1332+
*/
1333+
Assert.True(doc1.HashCode != null && doc2.HashCode != null && doc1.HashCode.Equals(doc2.HashCode));
1334+
Assert.Equal(doc1.HashCode, doc3.HashCode);
1335+
}
1336+
1337+
private static OpenApiDocument ParseInputFile(string filePath)
1338+
{
1339+
// Read in the input yaml file
1340+
using FileStream stream = File.OpenRead(filePath);
1341+
var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic);
1342+
1343+
return openApiDoc;
1344+
}
13171345
}
13181346
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
openapi : 3.0.0
2+
info:
3+
title: Simple Document
4+
version: 0.9.1
5+
paths: {}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
openapi : 3.0.0
2+
23
info:
34
title: Simple Document
4-
version: 0.9.1
55

6-
paths: {}
7-
6+
version: 0.9.1
87

8+
paths: {}
99

0 commit comments

Comments
 (0)