@ -865,35 +865,120 @@ var MatchkeysFunc = function.New(&function.Spec{
} ,
} )
// MergeFunc constructs a function that takes an arbitrary number of maps and
// returns a single map that contains a merged set of elements from all of the maps.
// MergeFunc constructs a function that takes an arbitrary number of maps or objects, and
// returns a single value that contains a merged set of keys and values from
// all of the inputs.
//
// If more than one given map defines the same key then the one that is later in
// the argument sequence takes precedence.
// If more than one given map or object defines the same key then the one that
// is later in the argument sequence takes precedence.
var MergeFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "maps" ,
Type : cty . DynamicPseudoType ,
AllowDynamicType : true ,
AllowNull : true ,
} ,
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
// empty args is accepted, so assume an empty object since we have no
// key-value types.
if len ( args ) == 0 {
return cty . EmptyObject , nil
}
// collect the possible object attrs
attrs := map [ string ] cty . Type { }
first := cty . NilType
matching := true
attrsKnown := true
for i , arg := range args {
ty := arg . Type ( )
// any dynamic args mean we can't compute a type
if ty . Equals ( cty . DynamicPseudoType ) {
return cty . DynamicPseudoType , nil
}
// check for invalid arguments
if ! ty . IsMapType ( ) && ! ty . IsObjectType ( ) {
return cty . NilType , fmt . Errorf ( "arguments must be maps or objects, got %#v" , ty . FriendlyName ( ) )
}
switch {
case ty . IsObjectType ( ) && ! arg . IsNull ( ) :
for attr , aty := range ty . AttributeTypes ( ) {
attrs [ attr ] = aty
}
case ty . IsMapType ( ) :
switch {
case arg . IsNull ( ) :
// pass, nothing to add
case arg . IsKnown ( ) :
ety := arg . Type ( ) . ElementType ( )
for it := arg . ElementIterator ( ) ; it . Next ( ) ; {
attr , _ := it . Element ( )
attrs [ attr . AsString ( ) ] = ety
}
default :
// any unknown maps means we don't know all possible attrs
// for the return type
attrsKnown = false
}
}
// record the first argument type for comparison
if i == 0 {
first = arg . Type ( )
continue
}
if ! ty . Equals ( first ) && matching {
matching = false
}
}
// the types all match, so use the first argument type
if matching {
return first , nil
}
// We had a mix of unknown maps and objects, so we can't predict the
// attributes
if ! attrsKnown {
return cty . DynamicPseudoType , nil
}
return cty . Object ( attrs ) , nil
} ,
Type : function . StaticReturnType ( cty . DynamicPseudoType ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
outputMap := make ( map [ string ] cty . Value )
// if all inputs are null, return a null value rather than an object
// with null attributes
allNull := true
for _ , arg := range args {
if ! arg . IsWhollyKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
if ! arg . Type ( ) . IsObjectType ( ) && ! arg . Type ( ) . IsMapType ( ) {
return cty . NilVal , fmt . Errorf ( "arguments must be maps or objects, got %#v" , arg . Type ( ) . FriendlyName ( ) )
if arg . IsNull ( ) {
continue
} else {
allNull = false
}
for it := arg . ElementIterator ( ) ; it . Next ( ) ; {
k , v := it . Element ( )
outputMap [ k . AsString ( ) ] = v
}
}
return cty . ObjectVal ( outputMap ) , nil
switch {
case allNull :
return cty . NullVal ( retType ) , nil
case retType . IsMapType ( ) :
return cty . MapVal ( outputMap ) , nil
case retType . IsObjectType ( ) , retType . Equals ( cty . DynamicPseudoType ) :
return cty . ObjectVal ( outputMap ) , nil
default :
panic ( fmt . Sprintf ( "unexpected return type: %#v" , retType ) )
}
} ,
} )