3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics ;
6
7
using System . IO ;
7
8
using System . Linq ;
8
9
using System . Net ;
9
10
using System . Net . Http ;
11
+ using System . Security ;
10
12
using System . Text ;
11
13
using System . Text . Json ;
14
+ using System . Threading . Tasks ;
15
+ using Microsoft . Extensions . Logging ;
12
16
using System . Xml . Linq ;
13
17
using Microsoft . OData . Edm . Csdl ;
14
18
using Microsoft . OpenApi . Extensions ;
21
25
22
26
namespace Microsoft . OpenApi . Hidi
23
27
{
24
- public static class OpenApiService
28
+ public class OpenApiService
25
29
{
26
- public static void ProcessOpenApiDocument (
30
+ public static async void ProcessOpenApiDocument (
27
31
string openapi ,
28
32
string csdl ,
29
33
FileInfo output ,
30
34
OpenApiSpecVersion ? version ,
31
35
OpenApiFormat ? format ,
36
+ LogLevel loglevel ,
37
+ bool inline ,
38
+ bool resolveexternal ,
32
39
string filterbyoperationids ,
33
40
string filterbytags ,
34
- string filterbycollection ,
35
- bool inline ,
36
- bool resolveexternal )
41
+ string filterbycollection
42
+ )
37
43
{
38
- if ( string . IsNullOrEmpty ( openapi ) && string . IsNullOrEmpty ( csdl ) )
44
+ var logger = ConfigureLoggerInstance ( loglevel ) ;
45
+
46
+ try
47
+ {
48
+ if ( string . IsNullOrEmpty ( openapi ) && string . IsNullOrEmpty ( csdl ) )
49
+ {
50
+ throw new ArgumentNullException ( "Please input a file path" ) ;
51
+ }
52
+ }
53
+ catch ( ArgumentNullException ex )
39
54
{
40
- throw new ArgumentNullException ( "Please input a file path" ) ;
55
+ logger . LogError ( ex . Message ) ;
56
+ return ;
41
57
}
42
- if ( output == null )
58
+ try
43
59
{
44
- throw new ArgumentException ( nameof ( output ) ) ;
60
+ if ( output == null )
61
+ {
62
+ throw new ArgumentException ( nameof ( output ) ) ;
63
+ }
45
64
}
46
- if ( output . Exists )
65
+ catch ( ArgumentException ex )
47
66
{
48
- throw new IOException ( "The file you're writing to already exists. Please input a new output path." ) ;
67
+ logger . LogError ( ex . Message ) ;
68
+ return ;
69
+ }
70
+ try
71
+ {
72
+ if ( output . Exists )
73
+ {
74
+ throw new IOException ( "The file you're writing to already exists. Please input a new file path." ) ;
75
+ }
76
+ }
77
+ catch ( IOException ex )
78
+ {
79
+ logger . LogError ( ex . Message ) ;
80
+ return ;
49
81
}
50
82
51
83
Stream stream ;
@@ -56,14 +88,18 @@ public static void ProcessOpenApiDocument(
56
88
{
57
89
// Default to yaml during csdl to OpenApi conversion
58
90
openApiFormat = format ?? GetOpenApiFormat ( csdl ) ;
59
-
60
- stream = GetStream ( csdl ) ;
61
- document = ConvertCsdlToOpenApi ( stream ) ;
91
+
92
+ stream = GetStream ( csdl ) ;
93
+ document = ConvertCsdlToOpenApi ( stream ) ;
62
94
}
63
95
else
64
96
{
65
- stream = GetStream ( openapi ) ;
97
+ stream = GetStream ( openapi , logger ) ;
66
98
99
+ // Parsing OpenAPI file
100
+ var stopwatch = new Stopwatch ( ) ;
101
+ stopwatch . Start ( ) ;
102
+ logger . LogTrace ( "Parsing OpenApi file" ) ;
67
103
var result = new OpenApiStreamReader ( new OpenApiReaderSettings
68
104
{
69
105
ReferenceResolution = resolveexternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
@@ -72,9 +108,9 @@ public static void ProcessOpenApiDocument(
72
108
) . ReadAsync ( stream ) . GetAwaiter ( ) . GetResult ( ) ;
73
109
74
110
document = result . OpenApiDocument ;
111
+ stopwatch . Stop ( ) ;
75
112
76
113
var context = result . OpenApiDiagnostic ;
77
-
78
114
if ( context . Errors . Count > 0 )
79
115
{
80
116
var errorReport = new StringBuilder ( ) ;
@@ -83,8 +119,11 @@ public static void ProcessOpenApiDocument(
83
119
{
84
120
errorReport . AppendLine ( error . ToString ( ) ) ;
85
121
}
86
-
87
- throw new ArgumentException ( string . Join ( Environment . NewLine , context . Errors . Select ( e => e . Message ) . ToArray ( ) ) ) ;
122
+ logger . LogError ( $ "{ stopwatch . ElapsedMilliseconds } ms: OpenApi Parsing errors { string . Join ( Environment . NewLine , context . Errors . Select ( e => e . Message ) . ToArray ( ) ) } ") ;
123
+ }
124
+ else
125
+ {
126
+ logger . LogTrace ( "{timestamp}ms: Parsed OpenApi successfully. {count} paths found." , stopwatch . ElapsedMilliseconds , document . Paths . Count ) ;
88
127
}
89
128
90
129
openApiFormat = format ?? GetOpenApiFormat ( openapi ) ;
@@ -93,32 +132,42 @@ public static void ProcessOpenApiDocument(
93
132
94
133
Func < string , OperationType ? , OpenApiOperation , bool > predicate ;
95
134
96
- // Check if filter options are provided, then execute
135
+ // Check if filter options are provided, then slice the OpenAPI document
97
136
if ( ! string . IsNullOrEmpty ( filterbyoperationids ) && ! string . IsNullOrEmpty ( filterbytags ) )
98
137
{
99
138
throw new InvalidOperationException ( "Cannot filter by operationIds and tags at the same time." ) ;
100
139
}
101
140
if ( ! string . IsNullOrEmpty ( filterbyoperationids ) )
102
141
{
142
+ logger . LogTrace ( "Creating predicate based on the operationIds supplied." ) ;
103
143
predicate = OpenApiFilterService . CreatePredicate ( operationIds : filterbyoperationids ) ;
144
+
145
+ logger . LogTrace ( "Creating subset OpenApi document." ) ;
104
146
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
105
147
}
106
148
if ( ! string . IsNullOrEmpty ( filterbytags ) )
107
149
{
150
+ logger . LogTrace ( "Creating predicate based on the tags supplied." ) ;
108
151
predicate = OpenApiFilterService . CreatePredicate ( tags : filterbytags ) ;
152
+
153
+ logger . LogTrace ( "Creating subset OpenApi document." ) ;
109
154
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
110
155
}
111
156
if ( ! string . IsNullOrEmpty ( filterbycollection ) )
112
157
{
113
- var fileStream = GetStream ( filterbycollection ) ;
114
- var requestUrls = ParseJsonCollectionFile ( fileStream ) ;
158
+ var fileStream = await GetStream ( filterbycollection , logger ) ;
159
+ var requestUrls = ParseJsonCollectionFile ( fileStream , logger ) ;
160
+
161
+ logger . LogTrace ( "Creating predicate based on the paths and Http methods defined in the Postman collection." ) ;
115
162
predicate = OpenApiFilterService . CreatePredicate ( requestUrls : requestUrls , source : document ) ;
163
+
164
+ logger . LogTrace ( "Creating subset OpenApi document." ) ;
116
165
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
117
166
}
118
167
168
+ logger . LogTrace ( "Creating a new file" ) ;
119
169
using var outputStream = output ? . Create ( ) ;
120
-
121
- var textWriter = outputStream != null ? new StreamWriter ( outputStream ) : Console . Out ;
170
+ var textWriter = outputStream != null ? new StreamWriter ( outputStream ) : Console . Out ;
122
171
123
172
var settings = new OpenApiWriterSettings ( )
124
173
{
@@ -131,7 +180,14 @@ public static void ProcessOpenApiDocument(
131
180
OpenApiFormat . Yaml => new OpenApiYamlWriter ( textWriter , settings ) ,
132
181
_ => throw new ArgumentException ( "Unknown format" ) ,
133
182
} ;
183
+
184
+ logger . LogTrace ( "Serializing to OpenApi document using the provided spec version and writer" ) ;
185
+
186
+ stopwatch . Start ( ) ;
134
187
document . Serialize ( writer , ( OpenApiSpecVersion ) version ) ;
188
+ stopwatch . Stop ( ) ;
189
+
190
+ logger . LogTrace ( $ "Finished serializing in { stopwatch . ElapsedMilliseconds } ms") ;
135
191
136
192
textWriter . Flush ( ) ;
137
193
}
@@ -184,26 +240,53 @@ public static OpenApiDocument FixReferences(OpenApiDocument document)
184
240
return doc ;
185
241
}
186
242
187
- private static Stream GetStream ( string input )
243
+ private static async Task < Stream > GetStream ( string input , ILogger logger )
188
244
{
245
+ var stopwatch = new Stopwatch ( ) ;
246
+ stopwatch . Start ( ) ;
247
+
189
248
Stream stream ;
190
249
if ( input . StartsWith ( "http" ) )
191
250
{
192
- var httpClient = new HttpClient ( new HttpClientHandler ( )
251
+ try
193
252
{
194
- SslProtocols = System . Security . Authentication . SslProtocols . Tls12 ,
195
- } )
253
+ using var httpClientHandler = new HttpClientHandler ( )
254
+ {
255
+ SslProtocols = System . Security . Authentication . SslProtocols . Tls12 ,
256
+ } ;
257
+ using var httpClient = new HttpClient ( httpClientHandler )
258
+ {
259
+ DefaultRequestVersion = HttpVersion . Version20
260
+ } ;
261
+ stream = await httpClient . GetStreamAsync ( input ) ;
262
+ }
263
+ catch ( HttpRequestException ex )
196
264
{
197
- DefaultRequestVersion = HttpVersion . Version20
198
- } ;
199
- stream = httpClient . GetStreamAsync ( input ) . Result ;
265
+ logger . LogError ( $ "Could not download the file at { input } , reason { ex } " ) ;
266
+ return null ;
267
+ }
200
268
}
201
269
else
202
270
{
203
- var fileInput = new FileInfo ( input ) ;
204
- stream = fileInput . OpenRead ( ) ;
271
+ try
272
+ {
273
+ var fileInput = new FileInfo ( input ) ;
274
+ stream = fileInput . OpenRead ( ) ;
275
+ }
276
+ catch ( Exception ex ) when ( ex is FileNotFoundException ||
277
+ ex is PathTooLongException ||
278
+ ex is DirectoryNotFoundException ||
279
+ ex is IOException ||
280
+ ex is UnauthorizedAccessException ||
281
+ ex is SecurityException ||
282
+ ex is NotSupportedException )
283
+ {
284
+ logger . LogError ( $ "Could not open the file at { input } , reason: { ex . Message } ") ;
285
+ return null ;
286
+ }
205
287
}
206
-
288
+ stopwatch . Stop ( ) ;
289
+ logger . LogTrace ( "{timestamp}ms: Read file {input}" , stopwatch . ElapsedMilliseconds , input ) ;
207
290
return stream ;
208
291
}
209
292
@@ -212,11 +295,11 @@ private static Stream GetStream(string input)
212
295
/// </summary>
213
296
/// <param name="stream"> A file stream.</param>
214
297
/// <returns> A dictionary of request urls and http methods from a collection.</returns>
215
- public static Dictionary < string , List < string > > ParseJsonCollectionFile ( Stream stream )
298
+ public static Dictionary < string , List < string > > ParseJsonCollectionFile ( Stream stream , ILogger logger )
216
299
{
217
300
var requestUrls = new Dictionary < string , List < string > > ( ) ;
218
301
219
- // Convert file to JsonDocument
302
+ logger . LogTrace ( "Parsing the json collection file into a JsonDocument" ) ;
220
303
using var document = JsonDocument . Parse ( stream ) ;
221
304
var root = document . RootElement ;
222
305
var itemElement = root . GetProperty ( "item" ) ;
@@ -235,21 +318,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
235
318
requestUrls [ path ] . Add ( method ) ;
236
319
}
237
320
}
238
-
321
+ logger . LogTrace ( "Finished fetching the list of paths and Http methods defined in the Postman collection." ) ;
239
322
return requestUrls ;
240
323
}
241
324
242
- internal static void ValidateOpenApiDocument ( string openapi )
325
+ internal static async void ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
243
326
{
244
- if ( openapi == null )
327
+ if ( string . IsNullOrEmpty ( openapi ) )
245
328
{
246
- throw new ArgumentNullException ( " openapi" ) ;
329
+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
247
330
}
248
-
249
- var stream = GetStream ( openapi ) ;
331
+ var logger = ConfigureLoggerInstance ( loglevel ) ;
332
+ var stream = await GetStream ( openapi , logger ) ;
250
333
251
334
OpenApiDocument document ;
252
-
335
+ logger . LogTrace ( "Parsing the OpenApi file" ) ;
253
336
document = new OpenApiStreamReader ( new OpenApiReaderSettings
254
337
{
255
338
RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
@@ -268,12 +351,33 @@ internal static void ValidateOpenApiDocument(string openapi)
268
351
var walker = new OpenApiWalker ( statsVisitor ) ;
269
352
walker . Walk ( document ) ;
270
353
354
+ logger . LogTrace ( "Finished walking through the OpenApi document. Generating a statistics report.." ) ;
271
355
Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
272
356
}
273
357
274
- private static OpenApiFormat GetOpenApiFormat ( string openapi )
358
+ private static OpenApiFormat GetOpenApiFormat ( string openapi , ILogger logger )
275
359
{
360
+ logger . LogTrace ( "Getting the OpenApi format" ) ;
276
361
return ! openapi . StartsWith ( "http" ) && Path . GetExtension ( openapi ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
277
362
}
363
+
364
+ private static ILogger ConfigureLoggerInstance ( LogLevel loglevel )
365
+ {
366
+ // Configure logger options
367
+ #if DEBUG
368
+ loglevel = loglevel > LogLevel . Debug ? LogLevel . Debug : loglevel ;
369
+ #endif
370
+
371
+ var logger = LoggerFactory . Create ( ( builder ) => {
372
+ builder
373
+ . AddConsole ( )
374
+ #if DEBUG
375
+ . AddDebug ( )
376
+ #endif
377
+ . SetMinimumLevel ( loglevel ) ;
378
+ } ) . CreateLogger < OpenApiService > ( ) ;
379
+
380
+ return logger ;
381
+ }
278
382
}
279
383
}
0 commit comments