github.com/avenga/couper@v1.12.2/eval/lib/merge.go (about)

     1  package lib
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/zclconf/go-cty/cty"
     7  	"github.com/zclconf/go-cty/cty/function"
     8  )
     9  
    10  var MergeFunc = newMergeFunction()
    11  
    12  func newMergeFunction() function.Function {
    13  	return function.New(&function.Spec{
    14  		Params: []function.Parameter{},
    15  		VarParam: &function.Parameter{
    16  			Name:             "maps",
    17  			Type:             cty.DynamicPseudoType,
    18  			AllowDynamicType: true,
    19  			AllowNull:        true,
    20  		},
    21  		Type: func(args []cty.Value) (cty.Type, error) {
    22  			// empty args is accepted, so assume an empty object since we have no
    23  			// key-value types.
    24  			if len(args) == 0 {
    25  				return cty.Bool, nil
    26  			}
    27  			return cty.DynamicPseudoType, nil
    28  		},
    29  		Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    30  			return Merge(args)
    31  		},
    32  	})
    33  }
    34  
    35  func Merge(args []cty.Value) (cty.Value, error) {
    36  	var t string
    37  	for _, arg := range args {
    38  		if arg.IsNull() {
    39  			continue
    40  		}
    41  		at := arg.Type()
    42  		if at.IsPrimitiveType() {
    43  			return cty.StringVal(""), errors.New("cannot merge primitive value")
    44  		}
    45  		if at.IsObjectType() || at.IsMapType() {
    46  			if t == "" {
    47  				t = "o"
    48  			} else if t != "o" {
    49  				return cty.StringVal(""), errors.New("type mismatch")
    50  			}
    51  		} else if at.IsTupleType() || at.IsListType() {
    52  			if t == "" {
    53  				t = "l"
    54  			} else if t != "l" {
    55  				return cty.StringVal(""), errors.New("type mismatch")
    56  			}
    57  		}
    58  	}
    59  	if t == "o" {
    60  		return mergeObjects(args), nil
    61  	}
    62  	if t == "l" {
    63  		return mergeTuples(args), nil
    64  	}
    65  	return cty.NullVal(cty.Bool), nil
    66  }
    67  
    68  func mergeObjects(args []cty.Value) cty.Value {
    69  	outputMap := make(map[string]cty.Value)
    70  	for _, arg := range args {
    71  		if arg.IsNull() {
    72  			continue
    73  		}
    74  
    75  		for it := arg.ElementIterator(); it.Next(); {
    76  			k, v := it.Element()
    77  			if existingVal, ok := outputMap[k.AsString()]; !ok {
    78  				// key not set
    79  				outputMap[k.AsString()] = v
    80  			} else if vType := v.Type(); vType.IsPrimitiveType() {
    81  				// primitive type
    82  				outputMap[k.AsString()] = v
    83  			} else if existingValType := existingVal.Type(); existingValType.IsObjectType() && (vType.IsObjectType() || vType.IsMapType()) {
    84  				outputMap[k.AsString()] = mergeObjects([]cty.Value{existingVal, v})
    85  			} else if existingValType.IsTupleType() && (vType.IsTupleType() || vType.IsListType()) {
    86  				outputMap[k.AsString()] = mergeTuples([]cty.Value{existingVal, v})
    87  			} else {
    88  				outputMap[k.AsString()] = v
    89  			}
    90  		}
    91  	}
    92  	return cty.ObjectVal(outputMap)
    93  }
    94  
    95  func mergeTuples(args []cty.Value) cty.Value {
    96  	var outputList []cty.Value
    97  	for _, arg := range args {
    98  		if arg.IsNull() {
    99  			continue
   100  		}
   101  		for it := arg.ElementIterator(); it.Next(); {
   102  			_, v := it.Element()
   103  			outputList = append(outputList, v)
   104  		}
   105  	}
   106  	return cty.TupleVal(outputList)
   107  }