1313using System . Text . Json ;
1414using System . Threading . Tasks ;
1515using Microsoft . Extensions . Logging ;
16+ using System . Xml . Linq ;
17+ using Microsoft . OData . Edm . Csdl ;
1618using Microsoft . OpenApi . Extensions ;
1719using Microsoft . OpenApi . Models ;
20+ using Microsoft . OpenApi . OData ;
1821using Microsoft . OpenApi . Readers ;
1922using Microsoft . OpenApi . Services ;
2023using Microsoft . OpenApi . Validations ;
@@ -24,8 +27,9 @@ namespace Microsoft.OpenApi.Hidi
2427{
2528 public class OpenApiService
2629 {
27- public static async void ProcessOpenApiDocument (
30+ public static async Task ProcessOpenApiDocument (
2831 string openapi ,
32+ string csdl ,
2933 FileInfo output ,
3034 OpenApiSpecVersion ? version ,
3135 OpenApiFormat ? format ,
@@ -41,9 +45,9 @@ string filterbycollection
4145
4246 try
4347 {
44- if ( string . IsNullOrEmpty ( openapi ) )
48+ if ( string . IsNullOrEmpty ( openapi ) && string . IsNullOrEmpty ( csdl ) )
4549 {
46- throw new ArgumentNullException ( nameof ( openapi ) ) ;
50+ throw new ArgumentNullException ( "Please input a file path" ) ;
4751 }
4852 }
4953 catch ( ArgumentNullException ex )
@@ -75,36 +79,56 @@ string filterbycollection
7579 logger . LogError ( ex . Message ) ;
7680 return ;
7781 }
78-
79- var stream = await GetStream ( openapi , logger ) ;
8082
81- // Parsing OpenAPI file
83+ Stream stream ;
84+ OpenApiDocument document ;
85+ OpenApiFormat openApiFormat ;
8286 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 ( ) ;
9387
94- var context = result . OpenApiDiagnostic ;
95- if ( context . Errors . Count > 0 )
88+ if ( ! string . IsNullOrEmpty ( csdl ) )
9689 {
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 ;
9893
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 ) ;
10496 }
10597 else
10698 {
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 ;
108132 }
109133
110134 Func < string , OperationType ? , OpenApiOperation , bool > predicate ;
@@ -151,8 +175,6 @@ string filterbycollection
151175 ReferenceInline = inline ? ReferenceInlineSetting . InlineLocalReferences : ReferenceInlineSetting . DoNotInlineReferences
152176 } ;
153177
154- var openApiFormat = format ?? GetOpenApiFormat ( openapi , logger ) ;
155- var openApiVersion = version ?? result . OpenApiDiagnostic . SpecificationVersion ;
156178 IOpenApiWriter writer = openApiFormat switch
157179 {
158180 OpenApiFormat . Json => new OpenApiJsonWriter ( textWriter , settings ) ,
@@ -163,14 +185,66 @@ string filterbycollection
163185 logger . LogTrace ( "Serializing to OpenApi document using the provided spec version and writer" ) ;
164186
165187 stopwatch . Start ( ) ;
166- document . Serialize ( writer , openApiVersion ) ;
188+ document . Serialize ( writer , ( OpenApiSpecVersion ) version ) ;
167189 stopwatch . Stop ( ) ;
168190
169191 logger . LogTrace ( $ "Finished serializing in { stopwatch . ElapsedMilliseconds } ms") ;
170192
171193 textWriter . Flush ( ) ;
172194 }
173195
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+
174248 private static async Task < Stream > GetStream ( string input , ILogger logger )
175249 {
176250 var stopwatch = new Stopwatch ( ) ;
@@ -181,13 +255,13 @@ private static async Task<Stream> GetStream(string input, ILogger logger)
181255 {
182256 try
183257 {
184- using var httpClientHandler = new HttpClientHandler ( )
258+ var httpClientHandler = new HttpClientHandler ( )
185259 {
186260 SslProtocols = System . Security . Authentication . SslProtocols . Tls12 ,
187261 } ;
188262 using var httpClient = new HttpClient ( httpClientHandler )
189263 {
190- DefaultRequestVersion = HttpVersion . Version20
264+ DefaultRequestVersion = HttpVersion . Version20
191265 } ;
192266 stream = await httpClient . GetStreamAsync ( input ) ;
193267 }
@@ -253,7 +327,7 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
253327 return requestUrls ;
254328 }
255329
256- internal static async void ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
330+ internal static async Task ValidateOpenApiDocument ( string openapi , LogLevel loglevel )
257331 {
258332 if ( string . IsNullOrEmpty ( openapi ) )
259333 {
@@ -286,10 +360,10 @@ internal static async void ValidateOpenApiDocument(string openapi, LogLevel logl
286360 Console . WriteLine ( statsVisitor . GetStatisticsReport ( ) ) ;
287361 }
288362
289- private static OpenApiFormat GetOpenApiFormat ( string openapi , ILogger logger )
363+ private static OpenApiFormat GetOpenApiFormat ( string input , ILogger logger )
290364 {
291365 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 ;
293367 }
294368
295369 private static ILogger ConfigureLoggerInstance ( LogLevel loglevel )
0 commit comments