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 ;
@@ -26,6 +29,7 @@ public class OpenApiService
26
29
{
27
30
public static async void 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 = 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,62 @@ 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 OpenApiDocument ConvertCsdlToOpenApi ( Stream csdl )
202
+ {
203
+ using var reader = new StreamReader ( csdl ) ;
204
+ var csdlText = reader . ReadToEndAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
205
+ var edmModel = CsdlReader . Parse ( XElement . Parse ( csdlText ) . CreateReader ( ) ) ;
206
+
207
+ var settings = new OpenApiConvertSettings ( )
208
+ {
209
+ EnableKeyAsSegment = true ,
210
+ EnableOperationId = true ,
211
+ PrefixEntityTypeNameBeforeKey = true ,
212
+ TagDepth = 2 ,
213
+ EnablePagination = true ,
214
+ EnableDiscriminatorValue = false ,
215
+ EnableDerivedTypesReferencesForRequestBody = false ,
216
+ EnableDerivedTypesReferencesForResponses = false ,
217
+ ShowRootPath = true ,
218
+ ShowLinks = true
219
+ } ;
220
+ OpenApiDocument document = edmModel . ConvertToOpenApi ( settings ) ;
221
+
222
+ document = FixReferences ( document ) ;
223
+
224
+ return document ;
225
+ }
226
+
227
+ /// <summary>
228
+ /// Fixes the references in the resulting OpenApiDocument.
229
+ /// </summary>
230
+ /// <param name="document"> The converted OpenApiDocument.</param>
231
+ /// <returns> A valid OpenApiDocument instance.</returns>
232
+ public static OpenApiDocument FixReferences ( OpenApiDocument document )
233
+ {
234
+ // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance.
235
+ // So we write it out, and read it back in again to fix it up.
236
+
237
+ var sb = new StringBuilder ( ) ;
238
+ document . SerializeAsV3 ( new OpenApiYamlWriter ( new StringWriter ( sb ) ) ) ;
239
+ var doc = new OpenApiStringReader ( ) . Read ( sb . ToString ( ) , out _ ) ;
240
+
241
+ return doc ;
242
+ }
243
+
174
244
private static async Task < Stream > GetStream ( string input , ILogger logger )
175
245
{
176
246
var stopwatch = new Stopwatch ( ) ;
@@ -286,10 +356,10 @@ internal static async void ValidateOpenApiDocument(string openapi, LogLevel logl
286
356
Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
287
357
}
288
358
289
- private static OpenApiFormat GetOpenApiFormat ( string openapi , ILogger logger )
359
+ private static OpenApiFormat GetOpenApiFormat ( string input , ILogger logger )
290
360
{
291
361
logger . LogTrace ( "Getting the OpenApi format" ) ;
292
- return ! openapi . StartsWith ( "http" ) && Path . GetExtension ( openapi ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
362
+ return ! input . StartsWith ( "http" ) && Path . GetExtension ( input ) == ".json" ? OpenApiFormat . Json : OpenApiFormat . Yaml ;
293
363
}
294
364
295
365
private static ILogger ConfigureLoggerInstance ( LogLevel loglevel )
0 commit comments