15
15
// specific language governing permissions and limitations
16
16
// under the License.
17
17
18
- use std:: collections:: HashMap ;
18
+ use std:: collections:: { HashMap , HashSet } ;
19
19
20
20
use anyhow:: bail;
21
21
use indexmap:: IndexMap ;
@@ -26,22 +26,43 @@ use crate::*;
26
26
struct Ctx {
27
27
new_types : IndexMap < TypeName , TypeDefinition > ,
28
28
types_seen : std:: collections:: HashSet < TypeName > ,
29
+ config : ExpandConfig ,
29
30
}
30
31
31
32
/// Generic parameters of a type
32
33
type GenericParams = Vec < TypeName > ;
33
- /// Generic arguments for an instanciated generic type
34
+ /// Generic arguments for an instantiated generic type
34
35
type GenericArgs = Vec < ValueOf > ;
35
36
/// Mapping from generic arguments to values
36
37
type GenericMapping = HashMap < TypeName , ValueOf > ;
37
38
38
- /// Expand all generics by creating new concrete types for every instanciation of a generic type.
39
+ #[ derive( Clone , Debug ) ]
40
+ pub struct ExpandConfig {
41
+ /// Generic types that will be inlined by replacing them with their definition, propagating generic arguments.
42
+ pub unwrap : HashSet < TypeName > ,
43
+ // Generic types that will be unwrapped by replacing them with their (single) generic parameter.
44
+ pub inline : HashSet < TypeName > ,
45
+ }
46
+
47
+ impl Default for ExpandConfig {
48
+ fn default ( ) -> Self {
49
+ ExpandConfig {
50
+ unwrap : Default :: default ( ) ,
51
+ inline : HashSet :: from ( [ builtins:: WITH_NULL_VALUE ] )
52
+ }
53
+ }
54
+ }
55
+
56
+ /// Expand all generics by creating new concrete types for every instantiation of a generic type.
39
57
///
40
58
/// The resulting model has no generics anymore. Top-level generic parameters (e.g. SearchRequest's TDocument) are
41
59
/// replaced by user_defined_data.
42
- pub fn expand_generics ( model : IndexedModel ) -> anyhow:: Result < IndexedModel > {
60
+ pub fn expand ( model : IndexedModel , config : ExpandConfig ) -> anyhow:: Result < IndexedModel > {
43
61
let mut model = model;
44
- let mut ctx = Ctx :: default ( ) ;
62
+ let mut ctx = Ctx {
63
+ config,
64
+ ..Ctx :: default ( )
65
+ } ;
45
66
46
67
for endpoint in & model. endpoints {
47
68
for name in [ & endpoint. request , & endpoint. response ] . into_iter ( ) . flatten ( ) {
@@ -317,6 +338,14 @@ pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
317
338
return Ok ( p. clone ( ) ) ;
318
339
}
319
340
341
+ // Inline or unwrap if required by the config
342
+ if ctx. config . inline . contains ( & inst. typ ) {
343
+ return inline_generic_type ( inst, mappings, model, ctx) ;
344
+ }
345
+ if ctx. config . unwrap . contains ( & inst. typ ) {
346
+ return unwrap_generic_type ( inst, mappings, model, ctx) ;
347
+ }
348
+
320
349
// Expand generic parameters, if any
321
350
let args = inst
322
351
. generics
@@ -346,6 +375,51 @@ pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
346
375
}
347
376
}
348
377
378
+ /// Inlines a value of a generic type by replacing it with its definition, propagating
379
+ /// generic arguments.
380
+ fn inline_generic_type (
381
+ value : & InstanceOf ,
382
+ _mappings : & GenericMapping ,
383
+ model : & IndexedModel ,
384
+ ctx : & mut Ctx ,
385
+ ) -> anyhow:: Result < ValueOf > {
386
+
387
+ // It has to be an alias (e.g. WithNullValue)
388
+ if let TypeDefinition :: TypeAlias ( inline_def) = model. get_type ( & value. typ ) ? {
389
+ // Create mappings to resolve types in the inlined type's definition
390
+ let mut inline_mappings = GenericMapping :: new ( ) ;
391
+ for ( source, dest) in inline_def. generics . iter ( ) . zip ( value. generics . iter ( ) ) {
392
+ inline_mappings. insert ( source. clone ( ) , dest. clone ( ) ) ;
393
+ }
394
+ // and expand the inlined type's alias definition
395
+ let result = expand_valueof ( & inline_def. typ , & inline_mappings, model, ctx) ?;
396
+ return Ok ( result) ;
397
+ } else {
398
+ bail ! ( "Expecting inlined type {} to be an alias" , & value. typ) ;
399
+ }
400
+ }
401
+
402
+ /// Unwraps a value of a generic type by replacing it with its generic parameter
403
+ fn unwrap_generic_type (
404
+ value : & InstanceOf ,
405
+ mappings : & GenericMapping ,
406
+ model : & IndexedModel ,
407
+ ctx : & mut Ctx ,
408
+ ) -> anyhow:: Result < ValueOf > {
409
+
410
+ // It has to be an alias (e.g. Stringified)
411
+ if let TypeDefinition :: TypeAlias ( _unwrap_def) = model. get_type ( & value. typ ) ? {
412
+ // Expand the inlined type's generic argument (there must be exactly one)
413
+ if value. generics . len ( ) != 1 {
414
+ bail ! ( "Expecting unwrapped type {} to have exactly one generic parameter" , & value. typ) ;
415
+ }
416
+ let result = expand_valueof ( & value. generics [ 0 ] , mappings, model, ctx) ?;
417
+ return Ok ( result) ;
418
+ } else {
419
+ bail ! ( "Expecting unwrapped type {} to be an alias" , & value. typ) ;
420
+ }
421
+ }
422
+
349
423
//---------------------------------------------------------------------------------------------
350
424
// Misc
351
425
//---------------------------------------------------------------------------------------------
@@ -422,12 +496,12 @@ mod tests {
422
496
423
497
let schema_json = std:: fs:: read_to_string ( "../../output/schema/schema.json" ) ?;
424
498
let model: IndexedModel = serde_json:: from_str ( & schema_json) ?;
425
- let model = expand_generics ( model) ?;
499
+ let model = expand ( model, ExpandConfig :: default ( ) ) ?;
426
500
427
501
let json_no_generics = serde_json:: to_string_pretty ( & model) ?;
428
502
429
503
if canonical_json != json_no_generics {
430
- std:: fs:: create_dir ( "test-output" ) ?;
504
+ std:: fs:: create_dir_all ( "test-output" ) ?;
431
505
let mut out = std:: fs:: File :: create ( "test-output/schema-no-generics-canonical.json" ) ?;
432
506
out. write_all ( canonical_json. as_bytes ( ) ) ?;
433
507
0 commit comments