33
44using System ;
55using System . Collections . Generic ;
6+ using System . Diagnostics ;
67using System . IO ;
78using System . Linq ;
89using System . Net ;
910using System . Net . Http ;
11+ using System . Security ;
1012using System . Text ;
1113using System . Text . Json ;
14+ using System . Threading . Tasks ;
15+ using Microsoft . Extensions . Logging ;
1216using Microsoft . OpenApi . Extensions ;
1317using Microsoft . OpenApi . Models ;
1418using Microsoft . OpenApi . Readers ;
1822
1923namespace 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