29
29
using Microsoft . Extensions . Configuration ;
30
30
using Microsoft . OpenApi . Hidi . Utilities ;
31
31
using Microsoft . OpenApi . Hidi . Formatters ;
32
+ using Microsoft . OpenApi . ApiManifest ;
33
+ using System . Linq ;
32
34
33
35
namespace Microsoft . OpenApi . Hidi
34
36
{
@@ -39,7 +41,7 @@ internal class OpenApiService
39
41
/// </summary>
40
42
public static async Task TransformOpenApiDocument ( HidiOptions options , ILogger logger , CancellationToken cancellationToken )
41
43
{
42
- if ( string . IsNullOrEmpty ( options . OpenApi ) && string . IsNullOrEmpty ( options . Csdl ) )
44
+ if ( string . IsNullOrEmpty ( options . OpenApi ) && string . IsNullOrEmpty ( options . Csdl ) && string . IsNullOrEmpty ( options . FilterOptions ? . FilterByApiManifest ) )
43
45
{
44
46
throw new ArgumentException ( "Please input a file path or URL" ) ;
45
47
}
@@ -65,11 +67,34 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
65
67
OpenApiFormat openApiFormat = options . OpenApiFormat ?? ( ! string . IsNullOrEmpty ( options . OpenApi ) ? GetOpenApiFormat ( options . OpenApi , logger ) : OpenApiFormat . Yaml ) ;
66
68
OpenApiSpecVersion openApiVersion = options . Version != null ? TryParseOpenApiSpecVersion ( options . Version ) : OpenApiSpecVersion . OpenApi3_0 ;
67
69
70
+ // If API Manifest is provided, load it, use it get the OpenAPI path
71
+ ApiManifestDocument apiManifest = null ;
72
+ if ( ! string . IsNullOrEmpty ( options . FilterOptions ? . FilterByApiManifest ) )
73
+ {
74
+ using ( var fileStream = await GetStream ( options . FilterOptions . FilterByApiManifest , logger , cancellationToken ) ) {
75
+ apiManifest = ApiManifestDocument . Load ( JsonDocument . Parse ( fileStream ) . RootElement ) ;
76
+ }
77
+ options . OpenApi = apiManifest . ApiDependencies [ 0 ] . ApiDescripionUrl ;
78
+ }
79
+
80
+ // If Postman Collection is provided, load it
81
+ JsonDocument postmanCollection = null ;
82
+ if ( ! String . IsNullOrEmpty ( options . FilterOptions ? . FilterByCollection ) )
83
+ {
84
+ using ( var collectionStream = await GetStream ( options . FilterOptions . FilterByCollection , logger , cancellationToken ) ) {
85
+ postmanCollection = JsonDocument . Parse ( collectionStream ) ;
86
+ }
87
+ }
88
+
89
+ // Load OpenAPI document
68
90
OpenApiDocument document = await GetOpenApi ( options . OpenApi , options . Csdl , options . CsdlFilter , options . SettingsConfig , options . InlineExternal , logger , cancellationToken , options . MetadataVersion ) ;
91
+
69
92
if ( options . FilterOptions != null )
70
- document = await FilterOpenApiDocument ( options . FilterOptions . FilterByOperationIds , options . FilterOptions . FilterByTags , options . FilterOptions . FilterByCollection , document , logger , cancellationToken ) ;
93
+ {
94
+ document = ApplyFilters ( options , logger , apiManifest , postmanCollection , document , cancellationToken ) ;
95
+ }
71
96
72
- var languageFormat = options . SettingsConfig . GetSection ( "LanguageFormat" ) . Value ;
97
+ var languageFormat = options . SettingsConfig ? . GetSection ( "LanguageFormat" ) ? . Value ;
73
98
if ( Extensions . StringExtensions . IsEquals ( languageFormat , "PowerShell" ) )
74
99
{
75
100
// PowerShell Walker.
@@ -93,6 +118,38 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
93
118
}
94
119
}
95
120
121
+ private static OpenApiDocument ApplyFilters ( HidiOptions options , ILogger logger , ApiManifestDocument apiManifest , JsonDocument postmanCollection , OpenApiDocument document , CancellationToken cancellationToken )
122
+ {
123
+ Dictionary < string , List < string > > requestUrls = null ;
124
+ if ( apiManifest != null )
125
+ {
126
+ requestUrls = GetRequestUrlsFromManifest ( apiManifest , document ) ;
127
+ }
128
+ else if ( postmanCollection != null )
129
+ {
130
+ requestUrls = EnumerateJsonDocument ( postmanCollection . RootElement , requestUrls ) ;
131
+ logger . LogTrace ( "Finished fetching the list of paths and Http methods defined in the Postman collection." ) ;
132
+ }
133
+
134
+
135
+ logger . LogTrace ( "Creating predicate from filter options." ) ;
136
+ var predicate = FilterOpenApiDocument ( options . FilterOptions . FilterByOperationIds ,
137
+ options . FilterOptions . FilterByTags ,
138
+ requestUrls ,
139
+ document ,
140
+ logger , cancellationToken ) ;
141
+ if ( predicate != null )
142
+ {
143
+ var stopwatch = new Stopwatch ( ) ;
144
+ stopwatch . Start ( ) ;
145
+ document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
146
+ stopwatch . Stop ( ) ;
147
+ logger . LogTrace ( "{timestamp}ms: Creating filtered OpenApi document with {paths} paths." , stopwatch . ElapsedMilliseconds , document . Paths . Count ) ;
148
+ }
149
+
150
+ return document ;
151
+ }
152
+
96
153
private static void WriteOpenApi ( FileInfo output , bool terseOutput , bool inlineLocal , bool inlineExternal , OpenApiFormat openApiFormat , OpenApiSpecVersion openApiVersion , OpenApiDocument document , ILogger logger )
97
154
{
98
155
using ( logger . BeginScope ( "Output" ) )
@@ -163,12 +220,12 @@ private static async Task<OpenApiDocument> GetOpenApi(string openapi, string csd
163
220
return document ;
164
221
}
165
222
166
- private static async Task < OpenApiDocument > FilterOpenApiDocument ( string filterbyoperationids , string filterbytags , string filterbycollection , OpenApiDocument document , ILogger logger , CancellationToken cancellationToken )
223
+ private static Func < string , OperationType ? , OpenApiOperation , bool > FilterOpenApiDocument ( string filterbyoperationids , string filterbytags , Dictionary < string , List < string > > requestUrls , OpenApiDocument document , ILogger logger , CancellationToken cancellationToken )
167
224
{
168
- using ( logger . BeginScope ( "Filter" ) )
169
- {
170
- Func < string , OperationType ? , OpenApiOperation , bool > predicate = null ;
225
+ Func < string , OperationType ? , OpenApiOperation , bool > predicate = null ;
171
226
227
+ using ( logger . BeginScope ( "Create Filter" ) )
228
+ {
172
229
// Check if filter options are provided, then slice the OpenAPI document
173
230
if ( ! string . IsNullOrEmpty ( filterbyoperationids ) && ! string . IsNullOrEmpty ( filterbytags ) )
174
231
{
@@ -186,25 +243,30 @@ private static async Task<OpenApiDocument> FilterOpenApiDocument(string filterby
186
243
predicate = OpenApiFilterService . CreatePredicate ( tags : filterbytags ) ;
187
244
188
245
}
189
- if ( ! string . IsNullOrEmpty ( filterbycollection ) )
246
+ if ( requestUrls != null )
190
247
{
191
- var fileStream = await GetStream ( filterbycollection , logger , cancellationToken ) ;
192
- var requestUrls = ParseJsonCollectionFile ( fileStream , logger ) ;
193
-
194
248
logger . LogTrace ( "Creating predicate based on the paths and Http methods defined in the Postman collection." ) ;
195
249
predicate = OpenApiFilterService . CreatePredicate ( requestUrls : requestUrls , source : document ) ;
196
250
}
197
- if ( predicate != null )
198
- {
199
- var stopwatch = new Stopwatch ( ) ;
200
- stopwatch . Start ( ) ;
201
- document = OpenApiFilterService . CreateFilteredDocument ( document , predicate ) ;
202
- stopwatch . Stop ( ) ;
203
- logger . LogTrace ( "{timestamp}ms: Creating filtered OpenApi document with {paths} paths." , stopwatch . ElapsedMilliseconds , document . Paths . Count ) ;
204
- }
205
251
}
206
252
207
- return document ;
253
+ return predicate ;
254
+ }
255
+
256
+ private static Dictionary < string , List < string > > GetRequestUrlsFromManifest ( ApiManifestDocument apiManifestDocument , OpenApiDocument document )
257
+ {
258
+ // Get the request URLs from the API Dependencies in the API manifest that have a baseURL that matches the server URL in the OpenAPI document
259
+ var serversUrls = document . Servers . Select ( s => s . Url ) ;
260
+ var requests = apiManifestDocument . ApiDependencies
261
+ . Where ( a => serversUrls . Any ( s => s == a . BaseUrl ) )
262
+ . SelectMany ( ad => ad . Requests . Where ( r => r . Exclude == false )
263
+ . Select ( r=> new { BaseUrl = ad . BaseUrl , UriTemplate = r . UriTemplate , Method = r . Method } ) )
264
+ . GroupBy ( r => r . BaseUrl . TrimEnd ( '/' ) + r . UriTemplate ) // The OpenApiFilterService expects non-relative URLs.
265
+ . ToDictionary ( g => g . Key , g => g . Select ( r => r . Method ) . ToList ( ) ) ;
266
+ // This makes the assumption that the UriTemplate in the ApiManifest matches exactly the UriTemplate in the OpenAPI document
267
+ // This does not need to be the case. The URI template in the API manifest could map to a set of OpenAPI paths.
268
+ // Additional logic will be required to handle this scenario. I sugggest we build this into the OpenAPI.Net library at some point.
269
+ return requests ;
208
270
}
209
271
210
272
private static XslCompiledTransform GetFilterTransform ( )
0 commit comments