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 Microsoft . OpenApi . Extensions ;
13
17
using Microsoft . OpenApi . Models ;
14
18
using Microsoft . OpenApi . Readers ;
18
22
19
23
namespace Microsoft . OpenApi . Hidi
20
24
{
21
- public static class OpenApiService
25
+ public class OpenApiService
22
26
{
23
- public static void ProcessOpenApiDocument (
24
- string input ,
27
+ public static async void ProcessOpenApiDocument (
28
+ string openapi ,
25
29
FileInfo output ,
26
30
OpenApiSpecVersion ? version ,
27
31
OpenApiFormat ? format ,
28
- string filterByOperationIds ,
29
- string filterByTags ,
30
- string filterByCollection ,
32
+ LogLevel loglevel ,
33
+ string filterbyoperationids ,
34
+ string filterbytags ,
35
+ string filterbycollection ,
31
36
bool inline ,
32
- bool resolveExternal )
37
+ bool resolveexternal )
33
38
{
34
- if ( string . IsNullOrEmpty ( input ) )
39
+ var logger = ConfigureLoggerInstance ( loglevel ) ;
40
+
41
+ try
35
42
{
36
- throw new ArgumentNullException ( nameof ( input ) ) ;
43
+ if ( string . IsNullOrEmpty ( openapi ) )
44
+ {
45
+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
46
+ }
37
47
}
38
- if ( output == null )
48
+ catch ( ArgumentNullException ex )
39
49
{
40
- throw new ArgumentException ( nameof ( output ) ) ;
50
+ logger . LogError ( ex . Message ) ;
51
+ return ;
41
52
}
42
- if ( output . Exists )
53
+ try
43
54
{
44
- throw new IOException ( "The file you're writing to already exists. Please input a new output path." ) ;
55
+ if ( output == null )
56
+ {
57
+ throw new ArgumentException ( nameof ( output ) ) ;
58
+ }
45
59
}
60
+ catch ( ArgumentException ex )
61
+ {
62
+ logger . LogError ( ex . Message ) ;
63
+ return ;
64
+ }
65
+ try
66
+ {
67
+ if ( output . Exists )
68
+ {
69
+ throw new IOException ( "The file you're writing to already exists. Please input a new file path." ) ;
70
+ }
71
+ }
72
+ catch ( IOException ex )
73
+ {
74
+ logger . LogError ( ex . Message ) ;
75
+ return ;
76
+ }
77
+
78
+ var stream = await GetStream ( openapi , logger ) ;
46
79
47
- var stream = GetStream ( input ) ;
80
+ // Parsing OpenAPI file
81
+ var stopwatch = new Stopwatch ( ) ;
82
+ stopwatch . Start ( ) ;
83
+ logger . LogTrace ( "Parsing OpenApi file" ) ;
48
84
var result = new OpenApiStreamReader ( new OpenApiReaderSettings
49
85
{
50
- ReferenceResolution = resolveExternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
86
+ ReferenceResolution = resolveexternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
51
87
RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
52
88
}
53
89
) . ReadAsync ( stream ) . GetAwaiter ( ) . GetResult ( ) ;
54
-
55
90
var document = result . OpenApiDocument ;
91
+ stopwatch . Stop ( ) ;
92
+
93
+ var context = result . OpenApiDiagnostic ;
94
+ if ( context . Errors . Count > 0 )
95
+ {
96
+ var errorReport = new StringBuilder ( ) ;
97
+
98
+ foreach ( var error in context . Errors )
99
+ {
100
+ errorReport . AppendLine ( error . ToString ( ) ) ;
101
+ }
102
+ logger . LogError ( $ "{ stopwatch . ElapsedMilliseconds } ms: OpenApi Parsing errors { string . Join ( Environment . NewLine , context . Errors . Select ( e => e . Message ) . ToArray ( ) ) } ") ;
103
+ }
104
+ else
105
+ {
106
+ logger . LogTrace ( "{timestamp}ms: Parsed OpenApi successfully. {count} paths found." , stopwatch . ElapsedMilliseconds , document . Paths . Count ) ;
107
+ }
108
+
56
109
Func < string , OperationType ? , OpenApiOperation , bool > predicate ;
57
110
58
- // Check if filter options are provided, then execute
59
- if ( ! string . IsNullOrEmpty ( filterByOperationIds ) && ! string . IsNullOrEmpty ( filterByTags ) )
111
+ // Check if filter options are provided, then slice the OpenAPI document
112
+ if ( ! string . IsNullOrEmpty ( filterbyoperationids ) && ! string . IsNullOrEmpty ( filterbytags ) )
60
113
{
61
114
throw new InvalidOperationException ( "Cannot filter by operationIds and tags at the same time." ) ;
62
115
}
63
- if ( ! string . IsNullOrEmpty ( filterByOperationIds ) )
116
+ if ( ! string . IsNullOrEmpty ( filterbyoperationids ) )
64
117
{
65
- predicate = OpenApiFilterService . CreatePredicate ( operationIds : filterByOperationIds ) ;
118
+ logger . LogTrace ( "Creating predicate based on the operationIds supplied." ) ;
119
+ predicate = OpenApiFilterService . CreatePredicate ( operationIds : filterbyoperationids ) ;
120
+
121
+ logger . LogTrace ( "Creating subset OpenApi document." ) ;
66
122
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
67
123
}
68
- if ( ! string . IsNullOrEmpty ( filterByTags ) )
124
+ if ( ! string . IsNullOrEmpty ( filterbytags ) )
69
125
{
70
- predicate = OpenApiFilterService . CreatePredicate ( tags : filterByTags ) ;
71
- document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
72
- }
126
+ logger . LogTrace ( "Creating predicate based on the tags supplied." ) ;
127
+ predicate = OpenApiFilterService . CreatePredicate ( tags : filterbytags ) ;
73
128
74
- if ( ! string . IsNullOrEmpty ( filterByCollection ) )
75
- {
76
- var fileStream = GetStream ( filterByCollection ) ;
77
- var requestUrls = ParseJsonCollectionFile ( fileStream ) ;
78
- predicate = OpenApiFilterService . CreatePredicate ( requestUrls : requestUrls , source : document ) ;
129
+ logger . LogTrace ( "Creating subset OpenApi document." ) ;
79
130
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
80
131
}
81
-
82
- var context = result . OpenApiDiagnostic ;
83
-
84
- if ( context . Errors . Count > 0 )
132
+ if ( ! string . IsNullOrEmpty ( filterbycollection ) )
85
133
{
86
- var errorReport = new StringBuilder ( ) ;
134
+ var fileStream = await GetStream ( filterbycollection , logger ) ;
135
+ var requestUrls = ParseJsonCollectionFile ( fileStream , logger ) ;
87
136
88
- foreach ( var error in context . Errors )
89
- {
90
- errorReport . AppendLine ( error . ToString ( ) ) ;
91
- }
137
+ logger . LogTrace ( "Creating predicate based on the paths and Http methods defined in the Postman collection." ) ;
138
+ predicate = OpenApiFilterService . CreatePredicate ( requestUrls : requestUrls , source : document ) ;
92
139
93
- throw new ArgumentException ( string . Join ( Environment . NewLine , context . Errors . Select ( e => e . Message ) . ToArray ( ) ) ) ;
140
+ logger . LogTrace ( "Creating subset OpenApi document." ) ;
141
+ document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
94
142
}
95
-
143
+
144
+ logger . LogTrace ( "Creating a new file" ) ;
96
145
using var outputStream = output ? . Create ( ) ;
97
-
98
- var textWriter = outputStream != null ? new StreamWriter ( outputStream ) : Console . Out ;
146
+ var textWriter = outputStream != null ? new StreamWriter ( outputStream ) : Console . Out ;
99
147
100
148
var settings = new OpenApiWriterSettings ( )
101
149
{
102
150
ReferenceInline = inline ? ReferenceInlineSetting . InlineLocalReferences : ReferenceInlineSetting . DoNotInlineReferences
103
151
} ;
104
152
105
- var openApiFormat = format ?? GetOpenApiFormat ( input ) ;
153
+ var openApiFormat = format ?? GetOpenApiFormat ( openapi , logger ) ;
106
154
var openApiVersion = version ?? result . OpenApiDiagnostic . SpecificationVersion ;
107
155
IOpenApiWriter writer = openApiFormat switch
108
156
{
109
157
OpenApiFormat . Json => new OpenApiJsonWriter ( textWriter , settings ) ,
110
158
OpenApiFormat . Yaml => new OpenApiYamlWriter ( textWriter , settings ) ,
111
159
_ => throw new ArgumentException ( "Unknown format" ) ,
112
160
} ;
161
+
162
+ logger . LogTrace ( "Serializing to OpenApi document using the provided spec version and writer" ) ;
163
+
164
+ stopwatch . Start ( ) ;
113
165
document . Serialize ( writer , openApiVersion ) ;
166
+ stopwatch . Stop ( ) ;
167
+
168
+ logger . LogTrace ( $ "Finished serializing in { stopwatch . ElapsedMilliseconds } ms") ;
114
169
115
170
textWriter . Flush ( ) ;
116
171
}
117
172
118
- private static Stream GetStream ( string input )
173
+ private static async Task < Stream > GetStream ( string input , ILogger logger )
119
174
{
175
+ var stopwatch = new Stopwatch ( ) ;
176
+ stopwatch . Start ( ) ;
177
+
120
178
Stream stream ;
121
179
if ( input . StartsWith ( "http" ) )
122
180
{
123
- var httpClient = new HttpClient ( new HttpClientHandler ( )
181
+ try
124
182
{
125
- SslProtocols = System . Security . Authentication . SslProtocols . Tls12 ,
126
- } )
183
+ using var httpClientHandler = new HttpClientHandler ( )
184
+ {
185
+ SslProtocols = System . Security . Authentication . SslProtocols . Tls12 ,
186
+ } ;
187
+ using var httpClient = new HttpClient ( httpClientHandler )
188
+ {
189
+ DefaultRequestVersion = HttpVersion . Version20
190
+ } ;
191
+ stream = await httpClient . GetStreamAsync ( input ) ;
192
+ }
193
+ catch ( HttpRequestException ex )
127
194
{
128
- DefaultRequestVersion = HttpVersion . Version20
129
- } ;
130
- stream = httpClient . GetStreamAsync ( input ) . Result ;
195
+ logger . LogError ( $ "Could not download the file at { input } , reason { ex } " ) ;
196
+ return null ;
197
+ }
131
198
}
132
199
else
133
200
{
134
- var fileInput = new FileInfo ( input ) ;
135
- stream = fileInput . OpenRead ( ) ;
201
+ try
202
+ {
203
+ var fileInput = new FileInfo ( input ) ;
204
+ stream = fileInput . OpenRead ( ) ;
205
+ }
206
+ catch ( Exception ex ) when ( ex is FileNotFoundException ||
207
+ ex is PathTooLongException ||
208
+ ex is DirectoryNotFoundException ||
209
+ ex is IOException ||
210
+ ex is UnauthorizedAccessException ||
211
+ ex is SecurityException ||
212
+ ex is NotSupportedException )
213
+ {
214
+ logger . LogError ( $ "Could not open the file at { input } , reason: { ex . Message } ") ;
215
+ return null ;
216
+ }
136
217
}
137
-
218
+ stopwatch . Stop ( ) ;
219
+ logger . LogTrace ( "{timestamp}ms: Read file {input}" , stopwatch . ElapsedMilliseconds , input ) ;
138
220
return stream ;
139
221
}
140
222
@@ -143,11 +225,11 @@ private static Stream GetStream(string input)
143
225
/// </summary>
144
226
/// <param name="stream"> A file stream.</param>
145
227
/// <returns> A dictionary of request urls and http methods from a collection.</returns>
146
- public static Dictionary < string , List < string > > ParseJsonCollectionFile ( Stream stream )
228
+ public static Dictionary < string , List < string > > ParseJsonCollectionFile ( Stream stream , ILogger logger )
147
229
{
148
230
var requestUrls = new Dictionary < string , List < string > > ( ) ;
149
231
150
- // Convert file to JsonDocument
232
+ logger . LogTrace ( "Parsing the json collection file into a JsonDocument" ) ;
151
233
using var document = JsonDocument . Parse ( stream ) ;
152
234
var root = document . RootElement ;
153
235
var itemElement = root . GetProperty ( "item" ) ;
@@ -166,21 +248,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
166
248
requestUrls [ path ] . Add ( method ) ;
167
249
}
168
250
}
169
-
251
+ logger . LogTrace ( "Finished fetching the list of paths and Http methods defined in the Postman collection." ) ;
170
252
return requestUrls ;
171
253
}
172
254
173
- internal static void ValidateOpenApiDocument ( string input )
255
+ internal static async void ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
174
256
{
175
- if ( input == null )
257
+ if ( string . IsNullOrEmpty ( openapi ) )
176
258
{
177
- throw new ArgumentNullException ( "input" ) ;
259
+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
178
260
}
179
-
180
- var stream = GetStream ( input ) ;
261
+ var logger = ConfigureLoggerInstance ( loglevel ) ;
262
+ var stream = await GetStream ( openapi , logger ) ;
181
263
182
264
OpenApiDocument document ;
183
-
265
+ logger . LogTrace ( "Parsing the OpenApi file" ) ;
184
266
document = new OpenApiStreamReader ( new OpenApiReaderSettings
185
267
{
186
268
RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
@@ -199,12 +281,33 @@ internal static void ValidateOpenApiDocument(string input)
199
281
var walker = new OpenApiWalker ( statsVisitor ) ;
200
282
walker . Walk ( document ) ;
201
283
284
+ logger . LogTrace ( "Finished walking through the OpenApi document. Generating a statistics report.." ) ;
202
285
Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
203
286
}
204
287
205
- private static OpenApiFormat GetOpenApiFormat ( string input )
288
+ private static OpenApiFormat GetOpenApiFormat ( string openapi , ILogger logger )
289
+ {
290
+ logger . LogTrace ( "Getting the OpenApi format" ) ;
291
+ return ! openapi . StartsWith ( "http" ) && Path . GetExtension ( openapi ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
292
+ }
293
+
294
+ private static ILogger ConfigureLoggerInstance ( LogLevel loglevel )
206
295
{
207
- return ! input . StartsWith ( "http" ) && Path . GetExtension ( input ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
296
+ // Configure logger options
297
+ #if DEBUG
298
+ loglevel = loglevel > LogLevel . Debug ? LogLevel . Debug : loglevel ;
299
+ #endif
300
+
301
+ var logger = LoggerFactory . Create ( ( builder ) => {
302
+ builder
303
+ . AddConsole ( )
304
+ #if DEBUG
305
+ . AddDebug ( )
306
+ #endif
307
+ . SetMinimumLevel ( loglevel ) ;
308
+ } ) . CreateLogger < OpenApiService > ( ) ;
309
+
310
+ return logger ;
208
311
}
209
312
}
210
313
}
0 commit comments