13
13
using System . Text . Json ;
14
14
using System . Threading . Tasks ;
15
15
using Microsoft . Extensions . Logging ;
16
+ using System . Xml . Linq ;
17
+ using Microsoft . OData . Edm . Csdl ;
16
18
using Microsoft . OpenApi . Extensions ;
17
19
using Microsoft . OpenApi . Models ;
20
+ using Microsoft . OpenApi . OData ;
18
21
using Microsoft . OpenApi . Readers ;
19
22
using Microsoft . OpenApi . Services ;
20
23
using Microsoft . OpenApi . Validations ;
@@ -24,8 +27,9 @@ namespace Microsoft.OpenApi.Hidi
24
27
{
25
28
public class OpenApiService
26
29
{
27
- public static async void ProcessOpenApiDocument (
30
+ public static async Task ProcessOpenApiDocument (
28
31
string openapi ,
32
+ string csdl ,
29
33
FileInfo output ,
30
34
OpenApiSpecVersion ? version ,
31
35
OpenApiFormat ? format ,
@@ -41,9 +45,9 @@ string filterbycollection
41
45
42
46
try
43
47
{
44
- if ( string . IsNullOrEmpty ( openapi ) )
48
+ if ( string . IsNullOrEmpty ( openapi ) && string . IsNullOrEmpty ( csdl ) )
45
49
{
46
- throw new ArgumentNullException ( nameof ( openapi ) ) ;
50
+ throw new ArgumentNullException ( "Please input a file path" ) ;
47
51
}
48
52
}
49
53
catch ( ArgumentNullException ex )
@@ -75,36 +79,56 @@ string filterbycollection
75
79
logger . LogError ( ex . Message ) ;
76
80
return ;
77
81
}
78
-
79
- var stream = await GetStream ( openapi , logger ) ;
80
82
81
- // Parsing OpenAPI file
83
+ Stream stream ;
84
+ OpenApiDocument document ;
85
+ OpenApiFormat openApiFormat ;
82
86
var stopwatch = new Stopwatch ( ) ;
83
- stopwatch . Start ( ) ;
84
- logger . LogTrace ( "Parsing OpenApi file" ) ;
85
- var result = new OpenApiStreamReader ( new OpenApiReaderSettings
86
- {
87
- ReferenceResolution = resolveexternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
88
- RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
89
- }
90
- ) . ReadAsync ( stream ) . GetAwaiter ( ) . GetResult ( ) ;
91
- var document = result . OpenApiDocument ;
92
- stopwatch . Stop ( ) ;
93
87
94
- var context = result . OpenApiDiagnostic ;
95
- if ( context . Errors . Count > 0 )
88
+ if ( ! string . IsNullOrEmpty ( csdl ) )
96
89
{
97
- var errorReport = new StringBuilder ( ) ;
90
+ // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion
91
+ openApiFormat = format ?? GetOpenApiFormat ( csdl , logger ) ;
92
+ version ??= OpenApiSpecVersion . OpenApi3_0 ;
98
93
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 ( ) ) } ") ;
94
+ stream = await GetStream ( csdl , logger ) ;
95
+ document = await ConvertCsdlToOpenApi ( stream ) ;
104
96
}
105
97
else
106
98
{
107
- logger . LogTrace ( "{timestamp}ms: Parsed OpenApi successfully. {count} paths found." , stopwatch . ElapsedMilliseconds , document . Paths . Count ) ;
99
+ stream = await GetStream ( openapi , logger ) ;
100
+
101
+ // Parsing OpenAPI file
102
+ stopwatch . Start ( ) ;
103
+ logger . LogTrace ( "Parsing OpenApi file" ) ;
104
+ var result = new OpenApiStreamReader ( new OpenApiReaderSettings
105
+ {
106
+ ReferenceResolution = resolveexternal ? ReferenceResolutionSetting . ResolveAllReferences : ReferenceResolutionSetting . ResolveLocalReferences ,
107
+ RuleSet = ValidationRuleSet . GetDefaultRuleSet ( )
108
+ }
109
+ ) . ReadAsync ( stream ) . GetAwaiter ( ) . GetResult ( ) ;
110
+
111
+ document = result . OpenApiDocument ;
112
+ stopwatch . Stop ( ) ;
113
+
114
+ var context = result . OpenApiDiagnostic ;
115
+ if ( context . Errors . Count > 0 )
116
+ {
117
+ var errorReport = new StringBuilder ( ) ;
118
+
119
+ foreach ( var error in context . Errors )
120
+ {
121
+ errorReport . AppendLine ( error . ToString ( ) ) ;
122
+ }
123
+ logger . LogError ( $ "{ stopwatch . ElapsedMilliseconds } ms: OpenApi Parsing errors { string . Join ( Environment . NewLine , context . Errors . Select ( e => e . Message ) . ToArray ( ) ) } ") ;
124
+ }
125
+ else
126
+ {
127
+ logger . LogTrace ( "{timestamp}ms: Parsed OpenApi successfully. {count} paths found." , stopwatch . ElapsedMilliseconds , document . Paths . Count ) ;
128
+ }
129
+
130
+ openApiFormat = format ?? GetOpenApiFormat ( openapi , logger ) ;
131
+ version ??= result . OpenApiDiagnostic . SpecificationVersion ;
108
132
}
109
133
110
134
Func < string , OperationType ? , OpenApiOperation , bool > predicate ;
@@ -151,8 +175,6 @@ string filterbycollection
151
175
ReferenceInline = inline ? ReferenceInlineSetting . InlineLocalReferences : ReferenceInlineSetting . DoNotInlineReferences
152
176
} ;
153
177
154
- var openApiFormat = format ?? GetOpenApiFormat ( openapi , logger ) ;
155
- var openApiVersion = version ?? result . OpenApiDiagnostic . SpecificationVersion ;
156
178
IOpenApiWriter writer = openApiFormat switch
157
179
{
158
180
OpenApiFormat . Json => new OpenApiJsonWriter ( textWriter , settings ) ,
@@ -163,14 +185,66 @@ string filterbycollection
163
185
logger . LogTrace ( "Serializing to OpenApi document using the provided spec version and writer" ) ;
164
186
165
187
stopwatch . Start ( ) ;
166
- document . Serialize ( writer , openApiVersion ) ;
188
+ document . Serialize ( writer , ( OpenApiSpecVersion ) version ) ;
167
189
stopwatch . Stop ( ) ;
168
190
169
191
logger . LogTrace ( $ "Finished serializing in { stopwatch . ElapsedMilliseconds } ms") ;
170
192
171
193
textWriter . Flush ( ) ;
172
194
}
173
195
196
+ /// <summary>
197
+ /// Converts CSDL to OpenAPI
198
+ /// </summary>
199
+ /// <param name="csdl">The CSDL stream.</param>
200
+ /// <returns>An OpenAPI document.</returns>
201
+ public static async Task < OpenApiDocument > ConvertCsdlToOpenApi ( Stream csdl )
202
+ {
203
+ using var reader = new StreamReader ( csdl ) ;
204
+ var csdlText = await reader . ReadToEndAsync ( ) ;
205
+ var edmModel = CsdlReader . Parse ( XElement . Parse ( csdlText ) . CreateReader ( ) ) ;
206
+
207
+ var settings = new OpenApiConvertSettings ( )
208
+ {
209
+ AddSingleQuotesForStringParameters = true ,
210
+ AddEnumDescriptionExtension = true ,
211
+ DeclarePathParametersOnPathItem = true ,
212
+ EnableKeyAsSegment = true ,
213
+ EnableOperationId = true ,
214
+ ErrorResponsesAsDefault = false ,
215
+ PrefixEntityTypeNameBeforeKey = true ,
216
+ TagDepth = 2 ,
217
+ EnablePagination = true ,
218
+ EnableDiscriminatorValue = false ,
219
+ EnableDerivedTypesReferencesForRequestBody = false ,
220
+ EnableDerivedTypesReferencesForResponses = false ,
221
+ ShowRootPath = true ,
222
+ ShowLinks = true
223
+ } ;
224
+ OpenApiDocument document = edmModel . ConvertToOpenApi ( settings ) ;
225
+
226
+ document = FixReferences ( document ) ;
227
+
228
+ return document ;
229
+ }
230
+
231
+ /// <summary>
232
+ /// Fixes the references in the resulting OpenApiDocument.
233
+ /// </summary>
234
+ /// <param name="document"> The converted OpenApiDocument.</param>
235
+ /// <returns> A valid OpenApiDocument instance.</returns>
236
+ public static OpenApiDocument FixReferences ( OpenApiDocument document )
237
+ {
238
+ // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance.
239
+ // So we write it out, and read it back in again to fix it up.
240
+
241
+ var sb = new StringBuilder ( ) ;
242
+ document . SerializeAsV3 ( new OpenApiYamlWriter ( new StringWriter ( sb ) ) ) ;
243
+ var doc = new OpenApiStringReader ( ) . Read ( sb . ToString ( ) , out _ ) ;
244
+
245
+ return doc ;
246
+ }
247
+
174
248
private static async Task < Stream > GetStream ( string input , ILogger logger )
175
249
{
176
250
var stopwatch = new Stopwatch ( ) ;
@@ -181,13 +255,13 @@ private static async Task<Stream> GetStream(string input, ILogger logger)
181
255
{
182
256
try
183
257
{
184
- using var httpClientHandler = new HttpClientHandler ( )
258
+ var httpClientHandler = new HttpClientHandler ( )
185
259
{
186
260
SslProtocols = System . Security . Authentication . SslProtocols . Tls12 ,
187
261
} ;
188
262
using var httpClient = new HttpClient ( httpClientHandler )
189
263
{
190
- DefaultRequestVersion = HttpVersion . Version20
264
+ DefaultRequestVersion = HttpVersion . Version20
191
265
} ;
192
266
stream = await httpClient . GetStreamAsync ( input ) ;
193
267
}
@@ -253,7 +327,7 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
253
327
return requestUrls ;
254
328
}
255
329
256
- internal static async void ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
330
+ internal static async Task ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
257
331
{
258
332
if ( string . IsNullOrEmpty ( openapi ) )
259
333
{
@@ -286,10 +360,10 @@ internal static async void ValidateOpenApiDocument(string openapi, LogLevel logl
286
360
Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
287
361
}
288
362
289
- private static OpenApiFormat GetOpenApiFormat ( string openapi , ILogger logger )
363
+ private static OpenApiFormat GetOpenApiFormat ( string input , ILogger logger )
290
364
{
291
365
logger . LogTrace ( "Getting the OpenApi format" ) ;
292
- return ! openapi . StartsWith ( "http" ) && Path . GetExtension ( openapi ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
366
+ return ! input . StartsWith ( "http" ) && Path . GetExtension ( input ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
293
367
}
294
368
295
369
private static ILogger ConfigureLoggerInstance ( LogLevel loglevel )
0 commit comments