Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 1 addition & 79 deletions src/Microsoft.OpenApi.Hidi/OpenApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,57 +356,7 @@ public static OpenApiDocument FixReferences(OpenApiDocument document)

return doc;
}

private static async Task<Stream> 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;
}


/// <summary>
/// Takes in a file stream, parses the stream into a JsonDocument and gets a list of paths and Http methods
/// </summary>
Expand Down Expand Up @@ -462,34 +412,6 @@ private static Dictionary<string, List<string>> EnumerateJsonDocument(JsonElemen
return paths;
}

/// <summary>
/// Fixes the references in the resulting OpenApiDocument.
/// </summary>
/// <param name="document"> The converted OpenApiDocument.</param>
/// <returns> A valid OpenApiDocument instance.</returns>
// 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;
// }

/// <summary>
/// Reads stream from file system or makes HTTP request depending on the input string
/// </summary>
Expand Down
1 change: 0 additions & 1 deletion src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic
{
diagnostic.Warnings.Add(item);
}

}

return document;
Expand Down
44 changes: 43 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.OpenApi.Exceptions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Services;
Expand Down Expand Up @@ -62,6 +65,11 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <summary>
/// The unique hash code of the generated OpenAPI document
/// </summary>
public string HashCode => GenerateHashValue(this);

/// <summary>
/// Parameter-less constructor
/// </summary>
Expand Down Expand Up @@ -375,6 +383,40 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
return ResolveReference(reference, false);
}

/// <summary>
/// Takes in an OpenApi document instance and generates its hash value
/// </summary>
/// <param name="doc">The OpenAPI description to hash.</param>
/// <returns>The hash value.</returns>
public static string GenerateHashValue(OpenApiDocument doc)
{
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();

cryptoStream.FlushFinalBlock();
var hash = sha.Hash;

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 < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}

return sb.ToString();
}

/// <summary>
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public void ShouldParseProducesInAnyOrder()
{
Type = ReferenceType.Schema,
Id = "Error",
HostDocument= doc
HostDocument = doc
},
Properties = new Dictionary<string, OpenApiSchema>()
{
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ public T Clone<T>(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;
Expand All @@ -48,7 +50,7 @@ public T Clone<T>(T element) where T : IOpenApiSerializable
}
}

public OpenApiSecurityScheme CloneSecurityScheme(OpenApiSecurityScheme element)
public OpenApiSecurityScheme CloneSecurityScheme(OpenApiSecurityScheme element)
{
using (var stream = new MemoryStream())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed()
}
}
}
},options => options.Excluding(m => m.Name == "HostDocument"));
}, options => options.Excluding(m => m.Name == "HostDocument"));
}
}

Expand Down
14 changes: 14 additions & 0 deletions test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Models\Samples\sampleDocument.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Models\Samples\sampleDocumentWithWhiteSpaces.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>

<None Update="PublicApi\PublicApi.approved.txt" CopyToOutputDirectory="Always" />
</ItemGroup>

<ItemGroup>
<Folder Include="Models\Samples\" />
</ItemGroup>
</Project>
30 changes: 29 additions & 1 deletion test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
openapi : 3.0.0
info:
title: Simple Document
version: 0.9.1
paths: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
openapi : 3.0.0

info:
title: Simple Document

version: 0.9.1

paths: {}

2 changes: 2 additions & 0 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ namespace Microsoft.OpenApi.Models
public Microsoft.OpenApi.Models.OpenApiComponents Components { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Interfaces.IOpenApiExtension> 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<Microsoft.OpenApi.Models.OpenApiSecurityRequirement> SecurityRequirements { get; set; }
Expand All @@ -535,6 +536,7 @@ namespace Microsoft.OpenApi.Models
public System.Collections.Generic.IEnumerable<Microsoft.OpenApi.Models.OpenApiError> 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
{
Expand Down