Skip to content

Commit f01c695

Browse files
authored
Merge pull request #716 from microsoft/release/1.3.1-preview3
Release/1.3.1 preview3
2 parents ad0bc0d + 19140e7 commit f01c695

File tree

9 files changed

+245
-87
lines changed

9 files changed

+245
-87
lines changed

src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
<LangVersion>9.0</LangVersion>
67
<PackAsTool>true</PackAsTool>
78
<ToolCommandName>hidi</ToolCommandName>
89
<PackageOutputPath>./../../artifacts</PackageOutputPath>
9-
<Version>0.5.0-preview2</Version>
10+
<Version>0.5.0-preview3</Version>
1011
</PropertyGroup>
1112

1213
<ItemGroup>
13-
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
14+
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
15+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
16+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
17+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
18+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta2.21617.1" />
1419
</ItemGroup>
1520

1621
<ItemGroup>

src/Microsoft.OpenApi.Hidi/OpenApiService.cs

Lines changed: 168 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.IO;
78
using System.Linq;
89
using System.Net;
910
using System.Net.Http;
11+
using System.Security;
1012
using System.Text;
1113
using System.Text.Json;
14+
using System.Threading.Tasks;
15+
using Microsoft.Extensions.Logging;
1216
using Microsoft.OpenApi.Extensions;
1317
using Microsoft.OpenApi.Models;
1418
using Microsoft.OpenApi.Readers;
@@ -18,123 +22,202 @@
1822

1923
namespace Microsoft.OpenApi.Hidi
2024
{
21-
public static class OpenApiService
25+
public class OpenApiService
2226
{
23-
public static void ProcessOpenApiDocument(
24-
string input,
27+
public static async void ProcessOpenApiDocument(
28+
string openapi,
2529
FileInfo output,
2630
OpenApiSpecVersion? version,
2731
OpenApiFormat? format,
28-
string filterByOperationIds,
29-
string filterByTags,
30-
string filterByCollection,
32+
LogLevel loglevel,
3133
bool inline,
32-
bool resolveExternal)
34+
bool resolveexternal,
35+
string filterbyoperationids,
36+
string filterbytags,
37+
string filterbycollection
38+
)
3339
{
34-
if (string.IsNullOrEmpty(input))
40+
var logger = ConfigureLoggerInstance(loglevel);
41+
42+
try
3543
{
36-
throw new ArgumentNullException(nameof(input));
44+
if (string.IsNullOrEmpty(openapi))
45+
{
46+
throw new ArgumentNullException(nameof(openapi));
47+
}
3748
}
38-
if(output == null)
49+
catch (ArgumentNullException ex)
3950
{
40-
throw new ArgumentException(nameof(output));
51+
logger.LogError(ex.Message);
52+
return;
4153
}
42-
if (output.Exists)
54+
try
4355
{
44-
throw new IOException("The file you're writing to already exists. Please input a new output path.");
56+
if(output == null)
57+
{
58+
throw new ArgumentException(nameof(output));
59+
}
4560
}
61+
catch (ArgumentException ex)
62+
{
63+
logger.LogError(ex.Message);
64+
return;
65+
}
66+
try
67+
{
68+
if (output.Exists)
69+
{
70+
throw new IOException("The file you're writing to already exists. Please input a new file path.");
71+
}
72+
}
73+
catch (IOException ex)
74+
{
75+
logger.LogError(ex.Message);
76+
return;
77+
}
78+
79+
var stream = await GetStream(openapi, logger);
4680

47-
var stream = GetStream(input);
81+
// Parsing OpenAPI file
82+
var stopwatch = new Stopwatch();
83+
stopwatch.Start();
84+
logger.LogTrace("Parsing OpenApi file");
4885
var result = new OpenApiStreamReader(new OpenApiReaderSettings
4986
{
50-
ReferenceResolution = resolveExternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
87+
ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
5188
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
5289
}
5390
).ReadAsync(stream).GetAwaiter().GetResult();
54-
5591
var document = result.OpenApiDocument;
92+
stopwatch.Stop();
93+
94+
var context = result.OpenApiDiagnostic;
95+
if (context.Errors.Count > 0)
96+
{
97+
var errorReport = new StringBuilder();
98+
99+
foreach (var error in context.Errors)
100+
{
101+
errorReport.AppendLine(error.ToString());
102+
}
103+
logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}");
104+
}
105+
else
106+
{
107+
logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
108+
}
109+
56110
Func<string, OperationType?, OpenApiOperation, bool> predicate;
57111

58-
// Check if filter options are provided, then execute
59-
if (!string.IsNullOrEmpty(filterByOperationIds) && !string.IsNullOrEmpty(filterByTags))
112+
// Check if filter options are provided, then slice the OpenAPI document
113+
if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags))
60114
{
61115
throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time.");
62116
}
63-
if (!string.IsNullOrEmpty(filterByOperationIds))
117+
if (!string.IsNullOrEmpty(filterbyoperationids))
64118
{
65-
predicate = OpenApiFilterService.CreatePredicate(operationIds: filterByOperationIds);
119+
logger.LogTrace("Creating predicate based on the operationIds supplied.");
120+
predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids);
121+
122+
logger.LogTrace("Creating subset OpenApi document.");
66123
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
67124
}
68-
if (!string.IsNullOrEmpty(filterByTags))
125+
if (!string.IsNullOrEmpty(filterbytags))
69126
{
70-
predicate = OpenApiFilterService.CreatePredicate(tags: filterByTags);
71-
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
72-
}
127+
logger.LogTrace("Creating predicate based on the tags supplied.");
128+
predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags);
73129

74-
if (!string.IsNullOrEmpty(filterByCollection))
75-
{
76-
var fileStream = GetStream(filterByCollection);
77-
var requestUrls = ParseJsonCollectionFile(fileStream);
78-
predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document);
130+
logger.LogTrace("Creating subset OpenApi document.");
79131
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
80132
}
81-
82-
var context = result.OpenApiDiagnostic;
83-
84-
if (context.Errors.Count > 0)
133+
if (!string.IsNullOrEmpty(filterbycollection))
85134
{
86-
var errorReport = new StringBuilder();
135+
var fileStream = await GetStream(filterbycollection, logger);
136+
var requestUrls = ParseJsonCollectionFile(fileStream, logger);
87137

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

93-
throw new ArgumentException(string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray()));
141+
logger.LogTrace("Creating subset OpenApi document.");
142+
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
94143
}
95-
144+
145+
logger.LogTrace("Creating a new file");
96146
using var outputStream = output?.Create();
97-
98-
var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
147+
var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
99148

100149
var settings = new OpenApiWriterSettings()
101150
{
102151
ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences
103152
};
104153

105-
var openApiFormat = format ?? GetOpenApiFormat(input);
154+
var openApiFormat = format ?? GetOpenApiFormat(openapi, logger);
106155
var openApiVersion = version ?? result.OpenApiDiagnostic.SpecificationVersion;
107156
IOpenApiWriter writer = openApiFormat switch
108157
{
109158
OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings),
110159
OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings),
111160
_ => throw new ArgumentException("Unknown format"),
112161
};
162+
163+
logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer");
164+
165+
stopwatch.Start();
113166
document.Serialize(writer, openApiVersion);
167+
stopwatch.Stop();
168+
169+
logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms");
114170

115171
textWriter.Flush();
116172
}
117173

118-
private static Stream GetStream(string input)
174+
private static async Task<Stream> GetStream(string input, ILogger logger)
119175
{
176+
var stopwatch = new Stopwatch();
177+
stopwatch.Start();
178+
120179
Stream stream;
121180
if (input.StartsWith("http"))
122181
{
123-
var httpClient = new HttpClient(new HttpClientHandler()
182+
try
124183
{
125-
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
126-
})
184+
using var httpClientHandler = new HttpClientHandler()
185+
{
186+
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
187+
};
188+
using var httpClient = new HttpClient(httpClientHandler)
189+
{
190+
DefaultRequestVersion = HttpVersion.Version20
191+
};
192+
stream = await httpClient.GetStreamAsync(input);
193+
}
194+
catch (HttpRequestException ex)
127195
{
128-
DefaultRequestVersion = HttpVersion.Version20
129-
};
130-
stream = httpClient.GetStreamAsync(input).Result;
196+
logger.LogError($"Could not download the file at {input}, reason{ex}");
197+
return null;
198+
}
131199
}
132200
else
133201
{
134-
var fileInput = new FileInfo(input);
135-
stream = fileInput.OpenRead();
202+
try
203+
{
204+
var fileInput = new FileInfo(input);
205+
stream = fileInput.OpenRead();
206+
}
207+
catch (Exception ex) when (ex is FileNotFoundException ||
208+
ex is PathTooLongException ||
209+
ex is DirectoryNotFoundException ||
210+
ex is IOException ||
211+
ex is UnauthorizedAccessException ||
212+
ex is SecurityException ||
213+
ex is NotSupportedException)
214+
{
215+
logger.LogError($"Could not open the file at {input}, reason: {ex.Message}");
216+
return null;
217+
}
136218
}
137-
219+
stopwatch.Stop();
220+
logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input);
138221
return stream;
139222
}
140223

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

150-
// Convert file to JsonDocument
233+
logger.LogTrace("Parsing the json collection file into a JsonDocument");
151234
using var document = JsonDocument.Parse(stream);
152235
var root = document.RootElement;
153236
var itemElement = root.GetProperty("item");
@@ -166,21 +249,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
166249
requestUrls[path].Add(method);
167250
}
168251
}
169-
252+
logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection.");
170253
return requestUrls;
171254
}
172255

173-
internal static void ValidateOpenApiDocument(string input)
256+
internal static async void ValidateOpenApiDocument(string openapi, LogLevel loglevel)
174257
{
175-
if (input == null)
258+
if (string.IsNullOrEmpty(openapi))
176259
{
177-
throw new ArgumentNullException("input");
260+
throw new ArgumentNullException(nameof(openapi));
178261
}
179-
180-
var stream = GetStream(input);
262+
var logger = ConfigureLoggerInstance(loglevel);
263+
var stream = await GetStream(openapi, logger);
181264

182265
OpenApiDocument document;
183-
266+
logger.LogTrace("Parsing the OpenApi file");
184267
document = new OpenApiStreamReader(new OpenApiReaderSettings
185268
{
186269
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
@@ -199,12 +282,33 @@ internal static void ValidateOpenApiDocument(string input)
199282
var walker = new OpenApiWalker(statsVisitor);
200283
walker.Walk(document);
201284

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

205-
private static OpenApiFormat GetOpenApiFormat(string input)
289+
private static OpenApiFormat GetOpenApiFormat(string openapi, ILogger logger)
290+
{
291+
logger.LogTrace("Getting the OpenApi format");
292+
return !openapi.StartsWith("http") && Path.GetExtension(openapi) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
293+
}
294+
295+
private static ILogger ConfigureLoggerInstance(LogLevel loglevel)
206296
{
207-
return !input.StartsWith("http") && Path.GetExtension(input) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
297+
// Configure logger options
298+
#if DEBUG
299+
loglevel = loglevel > LogLevel.Debug ? LogLevel.Debug : loglevel;
300+
#endif
301+
302+
var logger = LoggerFactory.Create((builder) => {
303+
builder
304+
.AddConsole()
305+
#if DEBUG
306+
.AddDebug()
307+
#endif
308+
.SetMinimumLevel(loglevel);
309+
}).CreateLogger<OpenApiService>();
310+
311+
return logger;
208312
}
209313
}
210314
}

0 commit comments

Comments
 (0)