Skip to content

Release/1.3.1 preview3 #716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4cc1db2
Align the input and output params with kiota
MaggieKimani1 Jan 24, 2022
fa4403e
Add aliases
MaggieKimani1 Jan 24, 2022
57312fa
Update the input command option for validate
MaggieKimani1 Jan 24, 2022
c1f37ee
Clean up code
MaggieKimani1 Jan 24, 2022
08d9b24
Bump FluentAssertions from 6.3.0 to 6.4.0
dependabot[bot] Jan 24, 2022
f81167e
Use kebab case for multi-name params
MaggieKimani1 Jan 27, 2022
2f6e323
Clean up code
MaggieKimani1 Jan 27, 2022
526aa29
MaggieKimani1 Jan 27, 2022
1ea0f5b
Add logging configurations
MaggieKimani1 Feb 1, 2022
f2ee8d5
Install packages
MaggieKimani1 Feb 1, 2022
5870012
Add a loglevel command option for additional logging
MaggieKimani1 Feb 1, 2022
d4fa9c7
Configure a mock logger for testing
MaggieKimani1 Feb 1, 2022
17bf0c2
Merge pull request #701 from microsoft/dependabot/nuget/FluentAsserti…
darrelmiller Feb 1, 2022
a70ac02
Bump Verify from 14.14.1 to 15.2.0
dependabot[bot] Feb 1, 2022
fb2bf58
Resolve PR feedback
MaggieKimani1 Feb 1, 2022
7db9b45
Merge pull request #708 from microsoft/dependabot/nuget/Verify-15.2.0
darrelmiller Feb 1, 2022
652379e
Bump Verify from 15.2.0 to 15.2.1
dependabot[bot] Feb 1, 2022
996b042
Merge branch 'vnext' into mk/align-hidi-params-with-kiota
MaggieKimani1 Feb 1, 2022
21925a3
Merge pull request #710 from microsoft/dependabot/nuget/Verify-15.2.1
darrelmiller Feb 1, 2022
a5e86dc
Merge branch 'vnext' into mk/align-hidi-params-with-kiota
darrelmiller Feb 1, 2022
ce24a81
Merge pull request #697 from microsoft/mk/align-hidi-params-with-kiota
darrelmiller Feb 1, 2022
93f6fe8
Merge remote-tracking branch 'origin/vnext' into mk/upgrade-commandli…
MaggieKimani1 Feb 1, 2022
f6a9654
Upgrades to System.Commandline beta2
MaggieKimani1 Feb 2, 2022
a750fcd
Merge pull request #713 from microsoft/mk/upgrade-commandline-to-beta2
darrelmiller Feb 2, 2022
126e1d7
Updated versions to preview3
darrelmiller Feb 2, 2022
5c01fbe
Merge pull request #714 from microsoft/release/1.3.1-preview3
darrelmiller Feb 2, 2022
19140e7
Merge branch 'master' into release/1.3.1-preview3
darrelmiller Feb 2, 2022
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
9 changes: 7 additions & 2 deletions src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>9.0</LangVersion>
<PackAsTool>true</PackAsTool>
<ToolCommandName>hidi</ToolCommandName>
<PackageOutputPath>./../../artifacts</PackageOutputPath>
<Version>0.5.0-preview2</Version>
<Version>0.5.0-preview3</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta2.21617.1" />
</ItemGroup>

<ItemGroup>
Expand Down
232 changes: 168 additions & 64 deletions src/Microsoft.OpenApi.Hidi/OpenApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
Expand All @@ -18,123 +22,202 @@

namespace Microsoft.OpenApi.Hidi
{
public static class OpenApiService
public class OpenApiService
{
public static void ProcessOpenApiDocument(
string input,
public static async void ProcessOpenApiDocument(
string openapi,
FileInfo output,
OpenApiSpecVersion? version,
OpenApiFormat? format,
string filterByOperationIds,
string filterByTags,
string filterByCollection,
LogLevel loglevel,
bool inline,
bool resolveExternal)
bool resolveexternal,
string filterbyoperationids,
string filterbytags,
string filterbycollection
)
{
if (string.IsNullOrEmpty(input))
var logger = ConfigureLoggerInstance(loglevel);

try
{
throw new ArgumentNullException(nameof(input));
if (string.IsNullOrEmpty(openapi))
{
throw new ArgumentNullException(nameof(openapi));
}
}
if(output == null)
catch (ArgumentNullException ex)
{
throw new ArgumentException(nameof(output));
logger.LogError(ex.Message);
return;
}
if (output.Exists)
try
{
throw new IOException("The file you're writing to already exists. Please input a new output path.");
if(output == null)
{
throw new ArgumentException(nameof(output));
}
}
catch (ArgumentException ex)
{
logger.LogError(ex.Message);
return;
}
try
{
if (output.Exists)
{
throw new IOException("The file you're writing to already exists. Please input a new file path.");
}
}
catch (IOException ex)
{
logger.LogError(ex.Message);
return;
}

var stream = await GetStream(openapi, logger);

var stream = GetStream(input);
// Parsing OpenAPI file
var stopwatch = new Stopwatch();
stopwatch.Start();
logger.LogTrace("Parsing OpenApi file");
var result = new OpenApiStreamReader(new OpenApiReaderSettings
{
ReferenceResolution = resolveExternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
}
).ReadAsync(stream).GetAwaiter().GetResult();

var document = result.OpenApiDocument;
stopwatch.Stop();

var context = result.OpenApiDiagnostic;
if (context.Errors.Count > 0)
{
var errorReport = new StringBuilder();

foreach (var error in context.Errors)
{
errorReport.AppendLine(error.ToString());
}
logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}");
}
else
{
logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
}

Func<string, OperationType?, OpenApiOperation, bool> predicate;

// Check if filter options are provided, then execute
if (!string.IsNullOrEmpty(filterByOperationIds) && !string.IsNullOrEmpty(filterByTags))
// Check if filter options are provided, then slice the OpenAPI document
if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags))
{
throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time.");
}
if (!string.IsNullOrEmpty(filterByOperationIds))
if (!string.IsNullOrEmpty(filterbyoperationids))
{
predicate = OpenApiFilterService.CreatePredicate(operationIds: filterByOperationIds);
logger.LogTrace("Creating predicate based on the operationIds supplied.");
predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids);

logger.LogTrace("Creating subset OpenApi document.");
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
}
if (!string.IsNullOrEmpty(filterByTags))
if (!string.IsNullOrEmpty(filterbytags))
{
predicate = OpenApiFilterService.CreatePredicate(tags: filterByTags);
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
}
logger.LogTrace("Creating predicate based on the tags supplied.");
predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags);

if (!string.IsNullOrEmpty(filterByCollection))
{
var fileStream = GetStream(filterByCollection);
var requestUrls = ParseJsonCollectionFile(fileStream);
predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document);
logger.LogTrace("Creating subset OpenApi document.");
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
}

var context = result.OpenApiDiagnostic;

if (context.Errors.Count > 0)
if (!string.IsNullOrEmpty(filterbycollection))
{
var errorReport = new StringBuilder();
var fileStream = await GetStream(filterbycollection, logger);
var requestUrls = ParseJsonCollectionFile(fileStream, logger);

foreach (var error in context.Errors)
{
errorReport.AppendLine(error.ToString());
}
logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection.");
predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document);

throw new ArgumentException(string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray()));
logger.LogTrace("Creating subset OpenApi document.");
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
}


logger.LogTrace("Creating a new file");
using var outputStream = output?.Create();

var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;

var settings = new OpenApiWriterSettings()
{
ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences
};

var openApiFormat = format ?? GetOpenApiFormat(input);
var openApiFormat = format ?? GetOpenApiFormat(openapi, logger);
var openApiVersion = version ?? result.OpenApiDiagnostic.SpecificationVersion;
IOpenApiWriter writer = openApiFormat switch
{
OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings),
OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings),
_ => throw new ArgumentException("Unknown format"),
};

logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer");

stopwatch.Start();
document.Serialize(writer, openApiVersion);
stopwatch.Stop();

logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms");

textWriter.Flush();
}

private static Stream GetStream(string input)
private static async Task<Stream> GetStream(string input, ILogger logger)
{
var stopwatch = new Stopwatch();
stopwatch.Start();

Stream stream;
if (input.StartsWith("http"))
{
var httpClient = new HttpClient(new HttpClientHandler()
try
{
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
})
using 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)
{
DefaultRequestVersion = HttpVersion.Version20
};
stream = httpClient.GetStreamAsync(input).Result;
logger.LogError($"Could not download the file at {input}, reason{ex}");
return null;
}
}
else
{
var fileInput = new FileInfo(input);
stream = fileInput.OpenRead();
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;
}

Expand All @@ -143,11 +226,11 @@ private static Stream GetStream(string input)
/// </summary>
/// <param name="stream"> A file stream.</param>
/// <returns> A dictionary of request urls and http methods from a collection.</returns>
public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream stream)
public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream stream, ILogger logger)
{
var requestUrls = new Dictionary<string, List<string>>();

// Convert file to JsonDocument
logger.LogTrace("Parsing the json collection file into a JsonDocument");
using var document = JsonDocument.Parse(stream);
var root = document.RootElement;
var itemElement = root.GetProperty("item");
Expand All @@ -166,21 +249,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
requestUrls[path].Add(method);
}
}

logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection.");
return requestUrls;
}

internal static void ValidateOpenApiDocument(string input)
internal static async void ValidateOpenApiDocument(string openapi, LogLevel loglevel)
{
if (input == null)
if (string.IsNullOrEmpty(openapi))
{
throw new ArgumentNullException("input");
throw new ArgumentNullException(nameof(openapi));
}

var stream = GetStream(input);
var logger = ConfigureLoggerInstance(loglevel);
var stream = await GetStream(openapi, logger);

OpenApiDocument document;

logger.LogTrace("Parsing the OpenApi file");
document = new OpenApiStreamReader(new OpenApiReaderSettings
{
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
Expand All @@ -199,12 +282,33 @@ internal static void ValidateOpenApiDocument(string input)
var walker = new OpenApiWalker(statsVisitor);
walker.Walk(document);

logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report..");
Console.WriteLine(statsVisitor.GetStatisticsReport());
}

private static OpenApiFormat GetOpenApiFormat(string input)
private static OpenApiFormat GetOpenApiFormat(string openapi, ILogger logger)
{
logger.LogTrace("Getting the OpenApi format");
return !openapi.StartsWith("http") && Path.GetExtension(openapi) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
}

private static ILogger ConfigureLoggerInstance(LogLevel loglevel)
{
return !input.StartsWith("http") && Path.GetExtension(input) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
// Configure logger options
#if DEBUG
loglevel = loglevel > LogLevel.Debug ? LogLevel.Debug : loglevel;
#endif

var logger = LoggerFactory.Create((builder) => {
builder
.AddConsole()
#if DEBUG
.AddDebug()
#endif
.SetMinimumLevel(loglevel);
}).CreateLogger<OpenApiService>();

return logger;
}
}
}
Loading