1515// specific language governing permissions and limitations
1616// under the License.
1717
18- use std:: collections:: HashMap ;
18+ use std:: collections:: { HashMap , HashSet } ;
1919
2020use anyhow:: bail;
2121use indexmap:: IndexMap ;
@@ -26,22 +26,43 @@ use crate::*;
2626struct Ctx {
2727 new_types : IndexMap < TypeName , TypeDefinition > ,
2828 types_seen : std:: collections:: HashSet < TypeName > ,
29+ config : ExpandConfig ,
2930}
3031
3132/// Generic parameters of a type
3233type GenericParams = Vec < TypeName > ;
33- /// Generic arguments for an instanciated generic type
34+ /// Generic arguments for an instantiated generic type
3435type GenericArgs = Vec < ValueOf > ;
3536/// Mapping from generic arguments to values
3637type GenericMapping = HashMap < TypeName , ValueOf > ;
3738
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.
3957///
4058/// The resulting model has no generics anymore. Top-level generic parameters (e.g. SearchRequest's TDocument) are
4159/// 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 > {
4361 let mut model = model;
44- let mut ctx = Ctx :: default ( ) ;
62+ let mut ctx = Ctx {
63+ config,
64+ ..Ctx :: default ( )
65+ } ;
4566
4667 for endpoint in & model. endpoints {
4768 for name in [ & endpoint. request , & endpoint. response ] . into_iter ( ) . flatten ( ) {
@@ -317,6 +338,14 @@ pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
317338 return Ok ( p. clone ( ) ) ;
318339 }
319340
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+
320349 // Expand generic parameters, if any
321350 let args = inst
322351 . generics
@@ -346,6 +375,51 @@ pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
346375 }
347376 }
348377
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+
349423 //---------------------------------------------------------------------------------------------
350424 // Misc
351425 //---------------------------------------------------------------------------------------------
@@ -422,12 +496,12 @@ mod tests {
422496
423497 let schema_json = std:: fs:: read_to_string ( "../../output/schema/schema.json" ) ?;
424498 let model: IndexedModel = serde_json:: from_str ( & schema_json) ?;
425- let model = expand_generics ( model) ?;
499+ let model = expand ( model, ExpandConfig :: default ( ) ) ?;
426500
427501 let json_no_generics = serde_json:: to_string_pretty ( & model) ?;
428502
429503 if canonical_json != json_no_generics {
430- std:: fs:: create_dir ( "test-output" ) ?;
504+ std:: fs:: create_dir_all ( "test-output" ) ?;
431505 let mut out = std:: fs:: File :: create ( "test-output/schema-no-generics-canonical.json" ) ?;
432506 out. write_all ( canonical_json. as_bytes ( ) ) ?;
433507
0 commit comments