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 ,
33+ string filterbyoperationids ,
34+ string filterbytags ,
35+ string filterbycollection ,
3136 bool inline ,
32- bool resolveExternal )
37+ bool resolveexternal )
3338 {
34- if ( string . IsNullOrEmpty ( input ) )
39+ var logger = ConfigureLoggerInstance ( loglevel ) ;
40+
41+ try
3542 {
36- throw new ArgumentNullException ( nameof ( input ) ) ;
43+ if ( string . IsNullOrEmpty ( openapi ) )
44+ {
45+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
46+ }
3747 }
38- if ( output == null )
48+ catch ( ArgumentNullException ex )
3949 {
40- throw new ArgumentException ( nameof ( output ) ) ;
50+ logger . LogError ( ex . Message ) ;
51+ return ;
4152 }
42- if ( output . Exists )
53+ try
4354 {
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+ }
4559 }
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 ) ;
4679
47- var stream = GetStream ( input ) ;
80+ // Parsing OpenAPI file
81+ var stopwatch = new Stopwatch ( ) ;
82+ stopwatch . Start ( ) ;
83+ logger . LogTrace ( "Parsing OpenApi file" ) ;
4884 var result = new OpenApiStreamReader ( new OpenApiReaderSettings
4985 {
50- ReferenceResolution = resolveExternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
86+ ReferenceResolution = resolveexternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
5187 RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
5288 }
5389 ) . ReadAsync ( stream ) . GetAwaiter ( ) . GetResult ( ) ;
54-
5590 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+
56109 Func < string , OperationType ? , OpenApiOperation , bool > predicate ;
57110
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 ) )
60113 {
61114 throw new InvalidOperationException ( "Cannot filter by operationIds and tags at the same time." ) ;
62115 }
63- if ( ! string . IsNullOrEmpty ( filterByOperationIds ) )
116+ if ( ! string . IsNullOrEmpty ( filterbyoperationids ) )
64117 {
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." ) ;
66122 document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
67123 }
68- if ( ! string . IsNullOrEmpty ( filterByTags ) )
124+ if ( ! string . IsNullOrEmpty ( filterbytags ) )
69125 {
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 ) ;
73128
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." ) ;
79130 document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
80131 }
81-
82- var context = result . OpenApiDiagnostic ;
83-
84- if ( context . Errors . Count > 0 )
132+ if ( ! string . IsNullOrEmpty ( filterbycollection ) )
85133 {
86- var errorReport = new StringBuilder ( ) ;
134+ var fileStream = await GetStream ( filterbycollection , logger ) ;
135+ var requestUrls = ParseJsonCollectionFile ( fileStream , logger ) ;
87136
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 ) ;
92139
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 ) ;
94142 }
95-
143+
144+ logger . LogTrace ( "Creating a new file" ) ;
96145 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 ;
99147
100148 var settings = new OpenApiWriterSettings ( )
101149 {
102150 ReferenceInline = inline ? ReferenceInlineSetting . InlineLocalReferences : ReferenceInlineSetting . DoNotInlineReferences
103151 } ;
104152
105- var openApiFormat = format ?? GetOpenApiFormat ( input ) ;
153+ var openApiFormat = format ?? GetOpenApiFormat ( openapi , logger ) ;
106154 var openApiVersion = version ?? result . OpenApiDiagnostic . SpecificationVersion ;
107155 IOpenApiWriter writer = openApiFormat switch
108156 {
109157 OpenApiFormat . Json => new OpenApiJsonWriter ( textWriter , settings ) ,
110158 OpenApiFormat . Yaml => new OpenApiYamlWriter ( textWriter , settings ) ,
111159 _ => throw new ArgumentException ( "Unknown format" ) ,
112160 } ;
161+
162+ logger . LogTrace ( "Serializing to OpenApi document using the provided spec version and writer" ) ;
163+
164+ stopwatch . Start ( ) ;
113165 document . Serialize ( writer , openApiVersion ) ;
166+ stopwatch . Stop ( ) ;
167+
168+ logger . LogTrace ( $ "Finished serializing in { stopwatch . ElapsedMilliseconds } ms") ;
114169
115170 textWriter . Flush ( ) ;
116171 }
117172
118- private static Stream GetStream ( string input )
173+ private static async Task < Stream > GetStream ( string input , ILogger logger )
119174 {
175+ var stopwatch = new Stopwatch ( ) ;
176+ stopwatch . Start ( ) ;
177+
120178 Stream stream ;
121179 if ( input . StartsWith ( "http" ) )
122180 {
123- var httpClient = new HttpClient ( new HttpClientHandler ( )
181+ try
124182 {
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 )
127194 {
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+ }
131198 }
132199 else
133200 {
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+ }
136217 }
137-
218+ stopwatch . Stop ( ) ;
219+ logger . LogTrace ( "{timestamp}ms: Read file {input}" , stopwatch . ElapsedMilliseconds , input ) ;
138220 return stream ;
139221 }
140222
@@ -143,11 +225,11 @@ private static Stream GetStream(string input)
143225 /// </summary>
144226 /// <param name="stream"> A file stream.</param>
145227 /// <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 )
147229 {
148230 var requestUrls = new Dictionary < string , List < string > > ( ) ;
149231
150- // Convert file to JsonDocument
232+ logger . LogTrace ( "Parsing the json collection file into a JsonDocument" ) ;
151233 using var document = JsonDocument . Parse ( stream ) ;
152234 var root = document . RootElement ;
153235 var itemElement = root . GetProperty ( "item" ) ;
@@ -166,21 +248,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
166248 requestUrls [ path ] . Add ( method ) ;
167249 }
168250 }
169-
251+ logger . LogTrace ( "Finished fetching the list of paths and Http methods defined in the Postman collection." ) ;
170252 return requestUrls ;
171253 }
172254
173- internal static void ValidateOpenApiDocument ( string input )
255+ internal static async void ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
174256 {
175- if ( input == null )
257+ if ( string . IsNullOrEmpty ( openapi ) )
176258 {
177- throw new ArgumentNullException ( "input" ) ;
259+ throw new ArgumentNullException ( nameof ( openapi ) ) ;
178260 }
179-
180- var stream = GetStream ( input ) ;
261+ var logger = ConfigureLoggerInstance ( loglevel ) ;
262+ var stream = await GetStream ( openapi , logger ) ;
181263
182264 OpenApiDocument document ;
183-
265+ logger . LogTrace ( "Parsing the OpenApi file" ) ;
184266 document = new OpenApiStreamReader ( new OpenApiReaderSettings
185267 {
186268 RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
@@ -199,12 +281,33 @@ internal static void ValidateOpenApiDocument(string input)
199281 var walker = new OpenApiWalker ( statsVisitor ) ;
200282 walker . Walk ( document ) ;
201283
284+ logger . LogTrace ( "Finished walking through the OpenApi document. Generating a statistics report.." ) ;
202285 Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
203286 }
204287
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 )
206295 {
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 ;
208311 }
209312 }
210313}
0 commit comments