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 ,
31
33
bool inline ,
32
- bool resolveExternal )
34
+ bool resolveexternal ,
35
+ string filterbyoperationids ,
36
+ string filterbytags ,
37
+ string filterbycollection
38
+ )
33
39
{
34
- if ( string . IsNullOrEmpty ( input ) )
40
+ var logger = ConfigureLoggerInstance ( loglevel ) ;
41
+
42
+ try
35
43
{
36
- throw new ArgumentNullException ( nameof ( input ) ) ;
44
+ if ( string . IsNullOrEmpty ( openapi ) )
45
+ {
46
+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
47
+ }
37
48
}
38
- if ( output == null )
49
+ catch ( ArgumentNullException ex )
39
50
{
40
- throw new ArgumentException ( nameof ( output ) ) ;
51
+ logger . LogError ( ex . Message ) ;
52
+ return ;
41
53
}
42
- if ( output . Exists )
54
+ try
43
55
{
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
+ }
45
60
}
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 ) ;
46
80
47
- var stream = GetStream ( input ) ;
81
+ // Parsing OpenAPI file
82
+ var stopwatch = new Stopwatch ( ) ;
83
+ stopwatch . Start ( ) ;
84
+ logger . LogTrace ( "Parsing OpenApi file" ) ;
48
85
var result = new OpenApiStreamReader ( new OpenApiReaderSettings
49
86
{
50
- ReferenceResolution = resolveExternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
87
+ ReferenceResolution = resolveexternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
51
88
RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
52
89
}
53
90
) . ReadAsync ( stream ) . GetAwaiter ( ) . GetResult ( ) ;
54
-
55
91
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
+
56
110
Func < string , OperationType ? , OpenApiOperation , bool > predicate ;
57
111
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 ) )
60
114
{
61
115
throw new InvalidOperationException ( "Cannot filter by operationIds and tags at the same time." ) ;
62
116
}
63
- if ( ! string . IsNullOrEmpty ( filterByOperationIds ) )
117
+ if ( ! string . IsNullOrEmpty ( filterbyoperationids ) )
64
118
{
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." ) ;
66
123
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
67
124
}
68
- if ( ! string . IsNullOrEmpty ( filterByTags ) )
125
+ if ( ! string . IsNullOrEmpty ( filterbytags ) )
69
126
{
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 ) ;
73
129
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." ) ;
79
131
document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
80
132
}
81
-
82
- var context = result . OpenApiDiagnostic ;
83
-
84
- if ( context . Errors . Count > 0 )
133
+ if ( ! string . IsNullOrEmpty ( filterbycollection ) )
85
134
{
86
- var errorReport = new StringBuilder ( ) ;
135
+ var fileStream = await GetStream ( filterbycollection , logger ) ;
136
+ var requestUrls = ParseJsonCollectionFile ( fileStream , logger ) ;
87
137
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 ) ;
92
140
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 ) ;
94
143
}
95
-
144
+
145
+ logger . LogTrace ( "Creating a new file" ) ;
96
146
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 ;
99
148
100
149
var settings = new OpenApiWriterSettings ( )
101
150
{
102
151
ReferenceInline = inline ? ReferenceInlineSetting . InlineLocalReferences : ReferenceInlineSetting . DoNotInlineReferences
103
152
} ;
104
153
105
- var openApiFormat = format ?? GetOpenApiFormat ( input ) ;
154
+ var openApiFormat = format ?? GetOpenApiFormat ( openapi , logger ) ;
106
155
var openApiVersion = version ?? result . OpenApiDiagnostic . SpecificationVersion ;
107
156
IOpenApiWriter writer = openApiFormat switch
108
157
{
109
158
OpenApiFormat . Json => new OpenApiJsonWriter ( textWriter , settings ) ,
110
159
OpenApiFormat . Yaml => new OpenApiYamlWriter ( textWriter , settings ) ,
111
160
_ => throw new ArgumentException ( "Unknown format" ) ,
112
161
} ;
162
+
163
+ logger . LogTrace ( "Serializing to OpenApi document using the provided spec version and writer" ) ;
164
+
165
+ stopwatch . Start ( ) ;
113
166
document . Serialize ( writer , openApiVersion ) ;
167
+ stopwatch . Stop ( ) ;
168
+
169
+ logger . LogTrace ( $ "Finished serializing in { stopwatch . ElapsedMilliseconds } ms") ;
114
170
115
171
textWriter . Flush ( ) ;
116
172
}
117
173
118
- private static Stream GetStream ( string input )
174
+ private static async Task < Stream > GetStream ( string input , ILogger logger )
119
175
{
176
+ var stopwatch = new Stopwatch ( ) ;
177
+ stopwatch . Start ( ) ;
178
+
120
179
Stream stream ;
121
180
if ( input . StartsWith ( "http" ) )
122
181
{
123
- var httpClient = new HttpClient ( new HttpClientHandler ( )
182
+ try
124
183
{
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 )
127
195
{
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
+ }
131
199
}
132
200
else
133
201
{
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
+ }
136
218
}
137
-
219
+ stopwatch . Stop ( ) ;
220
+ logger . LogTrace ( "{timestamp}ms: Read file {input}" , stopwatch . ElapsedMilliseconds , input ) ;
138
221
return stream ;
139
222
}
140
223
@@ -143,11 +226,11 @@ private static Stream GetStream(string input)
143
226
/// </summary>
144
227
/// <param name="stream"> A file stream.</param>
145
228
/// <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 )
147
230
{
148
231
var requestUrls = new Dictionary < string , List < string > > ( ) ;
149
232
150
- // Convert file to JsonDocument
233
+ logger . LogTrace ( "Parsing the json collection file into a JsonDocument" ) ;
151
234
using var document = JsonDocument . Parse ( stream ) ;
152
235
var root = document . RootElement ;
153
236
var itemElement = root . GetProperty ( "item" ) ;
@@ -166,21 +249,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
166
249
requestUrls [ path ] . Add ( method ) ;
167
250
}
168
251
}
169
-
252
+ logger . LogTrace ( "Finished fetching the list of paths and Http methods defined in the Postman collection." ) ;
170
253
return requestUrls ;
171
254
}
172
255
173
- internal static void ValidateOpenApiDocument ( string input )
256
+ internal static async void ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
174
257
{
175
- if ( input == null )
258
+ if ( string . IsNullOrEmpty ( openapi ) )
176
259
{
177
- throw new ArgumentNullException ( "input" ) ;
260
+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
178
261
}
179
-
180
- var stream = GetStream ( input ) ;
262
+ var logger = ConfigureLoggerInstance ( loglevel ) ;
263
+ var stream = await GetStream ( openapi , logger ) ;
181
264
182
265
OpenApiDocument document ;
183
-
266
+ logger . LogTrace ( "Parsing the OpenApi file" ) ;
184
267
document = new OpenApiStreamReader ( new OpenApiReaderSettings
185
268
{
186
269
RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
@@ -199,12 +282,33 @@ internal static void ValidateOpenApiDocument(string input)
199
282
var walker = new OpenApiWalker ( statsVisitor ) ;
200
283
walker . Walk ( document ) ;
201
284
285
+ logger . LogTrace ( "Finished walking through the OpenApi document. Generating a statistics report.." ) ;
202
286
Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
203
287
}
204
288
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 )
206
296
{
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 ;
208
312
}
209
313
}
210
314
}
0 commit comments