1
+ import 'dart:convert' ;
2
+ import 'dart:io' ;
3
+
4
+ import 'package:crypto/crypto.dart' ;
5
+ import 'package:meta/meta.dart' ;
6
+
1
7
/// Config base class
2
8
/// Your annotated class must extend this config class
3
9
@Deprecated (
@@ -23,8 +29,22 @@ class Openapi {
23
29
/// relative path or url to spec file
24
30
///
25
31
/// -i
32
+ @Deprecated ('To be removed in the next major' )
26
33
final String inputSpecFile;
27
34
35
+ /// Provides the access information to the input spec file.
36
+ ///
37
+ /// For use with useNextGen.
38
+ ///
39
+ /// The next generation of the OAS spec file information. Allows for local and
40
+ /// remote spec files.
41
+ ///
42
+ /// This version of the spec file configuration allows for custom authorization
43
+ /// to be applied to the fetch request when the spec file is in a remote
44
+ /// location. There is also special handling for when the spec file lives within
45
+ /// AWS.
46
+ final InputSpec ? inputSpec;
47
+
28
48
/// folder containing the template files
29
49
///
30
50
/// You can read more about templating here
@@ -133,6 +153,7 @@ class Openapi {
133
153
this .overwriteExistingFiles,
134
154
this .skipSpecValidation = false ,
135
155
required this .inputSpecFile,
156
+ this .inputSpec,
136
157
this .templateDirectory,
137
158
required this .generatorName,
138
159
this .outputDirectory,
@@ -152,6 +173,170 @@ class Openapi {
152
173
});
153
174
}
154
175
176
+ /// Provides the input spec file to be used.
177
+ ///
178
+ /// Provides the location of the input spec file to be used by the generator.
179
+ /// Includes the option to use the default json or yaml paths.
180
+ class InputSpec {
181
+ final String path;
182
+ final bool defaultYaml;
183
+ final bool useYml;
184
+
185
+ const InputSpec ({String ? path, this .defaultYaml = true , this .useYml = false })
186
+ : path = path ??
187
+ 'openapi.${defaultYaml ? 'y${useYml ? '' : 'a' }ml' : 'json' }' ;
188
+
189
+ const InputSpec .empty () : this ();
190
+
191
+ const InputSpec .emptyJson () : this (defaultYaml: false );
192
+ const InputSpec .emptyYml () : this (useYml: true );
193
+
194
+ Map <String , dynamic > toJsonMap () => {
195
+ 'path' : path,
196
+ 'defaultYaml' : defaultYaml,
197
+ 'useYml' : useYml,
198
+ };
199
+
200
+ InputSpec .fromMap (Map <String , dynamic > map)
201
+ : this (
202
+ path: map['path' ],
203
+ defaultYaml: map['defaultYaml' ] == 'true' ? true : false ,
204
+ useYml: map['useYml' ] == 'true' ? true : false ,
205
+ );
206
+ }
207
+
208
+ /// Provides the location for the remote specification.
209
+ ///
210
+ /// Provides basic support for fetching remote specification files hidden behind
211
+ /// an authenticated endpoint.
212
+ ///
213
+ /// By default when no [url] is provided a default [Uri.http] to
214
+ /// localhost:8080/ is used.
215
+ ///
216
+ /// This contains authentication information for fetching the OAS spec ONLY. This
217
+ /// does not apply security to the entry points defined in the OAS spec.
218
+ class RemoteSpec extends InputSpec {
219
+ final RemoteSpecHeaderDelegate headerDelegate;
220
+
221
+ const RemoteSpec ({
222
+ required String path,
223
+ this .headerDelegate = const RemoteSpecHeaderDelegate (),
224
+ }) : super (path: path);
225
+
226
+ const RemoteSpec .empty () : this (path: 'http://localhost:8080/' );
227
+
228
+ Uri get url => Uri .parse (path);
229
+
230
+ RemoteSpec .fromMap (Map <String , dynamic > map)
231
+ : headerDelegate =
232
+ map['headerDelegate' ] ?? const RemoteSpecHeaderDelegate (),
233
+ super .fromMap (map);
234
+ }
235
+
236
+ /// Default [RemoteSpecHeaderDelegate] used when retrieving a remote OAS spec.
237
+ class RemoteSpecHeaderDelegate {
238
+ const RemoteSpecHeaderDelegate ();
239
+
240
+ Map <String , String >? header () => null ;
241
+
242
+ RemoteSpecHeaderDelegate .fromMap (Map <String , dynamic > map) : this ();
243
+ }
244
+
245
+ /// Indicates whether or not the spec file live within AWS.
246
+ ///
247
+ /// Since AWS handles the authentication header differently, we need to inform
248
+ /// the builder to include the alternate auth header.
249
+ ///
250
+ /// This currently only supports AWS Object GET.
251
+ ///
252
+ /// This contains authentication information for fetching the OAS spec ONLY. This
253
+ /// does not apply security to the entry points defined in the OAS spec.
254
+ ///
255
+ /// This delegate makes the assumption that the AWS credentials to be used are
256
+ /// provided by the environment and are not empty.
257
+ class AWSRemoteSpecHeaderDelegate extends RemoteSpecHeaderDelegate {
258
+ /// The [bucket] where the OAS spec is stored within AWS.
259
+ final String bucket;
260
+ final String ? accessKeyId;
261
+ final String ? secretAccessKey;
262
+
263
+ const AWSRemoteSpecHeaderDelegate ({
264
+ required this .bucket,
265
+ this .secretAccessKey = null ,
266
+ this .accessKeyId = null ,
267
+ }) : super ();
268
+
269
+ AWSRemoteSpecHeaderDelegate .fromMap (Map <String , dynamic > map)
270
+ : bucket = map['bucket' ],
271
+ accessKeyId = map['accessKeyId' ],
272
+ secretAccessKey = map['secretAccessKey' ],
273
+ super .fromMap (map);
274
+
275
+ /// Generates the [header] map used within the GET request.
276
+ ///
277
+ /// Assumes that the user's auth AWS credentials
278
+ @override
279
+ Map <String , String >? header ({
280
+ String ? path,
281
+ }) {
282
+ if (! (path != null && path.isNotEmpty)) {
283
+ throw new AssertionError ('The path to the OAS spec should be provided' );
284
+ }
285
+
286
+ // Use the provided credentials to the constructor, if any, otherwise
287
+ // fallback to the environment values or throw.
288
+ final accessKey = accessKeyId ?? Platform .environment['AWS_ACCESS_KEY_ID' ];
289
+ final secretKey =
290
+ secretAccessKey ?? Platform .environment['AWS_SECRET_ACCESS_KEY' ];
291
+ if ((accessKey == null || accessKey.isEmpty) ||
292
+ (secretKey == null || secretKey.isEmpty)) {
293
+ throw new AssertionError (
294
+ 'AWS_SECRET_KEY_ID & AWS_SECRET_ACCESS_KEY should be defined and not empty or they should be provided in the delegate constructor.' );
295
+ }
296
+
297
+ final now = DateTime .now ();
298
+
299
+ return {
300
+ 'Authorization' : authHeaderContent (
301
+ now: now,
302
+ bucket: bucket,
303
+ path: path,
304
+ accessKeyId: accessKey,
305
+ secretAccessKey: secretKey,
306
+ ),
307
+ 'x-amz-date' : now.toIso8601String (),
308
+ };
309
+ }
310
+
311
+ /// The [Authentication] header content.
312
+ ///
313
+ /// This builds the Authorization header content format used by AWS.
314
+ @visibleForTesting
315
+ String authHeaderContent ({
316
+ required DateTime now,
317
+ required String bucket,
318
+ required String path,
319
+ required String accessKeyId,
320
+ required String secretAccessKey,
321
+ }) {
322
+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html#RESTAuthenticationExamples
323
+ String toSign = [
324
+ 'GET' ,
325
+ '' ,
326
+ '' ,
327
+ now.toIso8601String (),
328
+ '/$bucket /$path ' ,
329
+ ].join ('\n ' );
330
+
331
+ final utf8AKey = utf8.encode (secretAccessKey);
332
+ final utf8ToSign = utf8.encode (toSign);
333
+
334
+ final signature =
335
+ base64Encode (Hmac (sha1, utf8AKey).convert (utf8ToSign).bytes);
336
+ return 'AWS $accessKeyId :$signature ' ;
337
+ }
338
+ }
339
+
155
340
class AdditionalProperties {
156
341
/// toggles whether unicode identifiers are allowed in names or not, default is false
157
342
final bool ? allowUnicodeIdentifiers;
0 commit comments