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