github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/jobspec2/parse_map.go (about)

     1  package jobspec2
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/big"
     7  	"reflect"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/mitchellh/reflectwalk"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  // decodeMapInterfaceType decodes hcl instances of `map[string]interface{}` fields
    15  // of v.
    16  //
    17  // The HCL parser stores the hcl AST as the map values, and decodeMapInterfaceType
    18  // evaluates the AST and converts them to the native golang types.
    19  func decodeMapInterfaceType(v interface{}, ctx *hcl.EvalContext) hcl.Diagnostics {
    20  	w := &walker{ctx: ctx}
    21  	err := reflectwalk.Walk(v, w)
    22  	if err != nil {
    23  		w.diags = append(w.diags, &hcl.Diagnostic{
    24  			Severity: hcl.DiagError,
    25  			Summary:  "unexpected internal error",
    26  			Detail:   err.Error(),
    27  		})
    28  	}
    29  	return w.diags
    30  }
    31  
    32  type walker struct {
    33  	ctx   *hcl.EvalContext
    34  	diags hcl.Diagnostics
    35  }
    36  
    37  var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
    38  
    39  func (w *walker) Map(m reflect.Value) error {
    40  	if !m.Type().AssignableTo(mapStringInterfaceType) {
    41  		return nil
    42  	}
    43  
    44  	// ignore private map fields
    45  	if !m.CanSet() {
    46  		return nil
    47  	}
    48  
    49  	for _, k := range m.MapKeys() {
    50  		v := m.MapIndex(k)
    51  		if attr, ok := v.Interface().(*hcl.Attribute); ok {
    52  			c, diags := decodeInterface(attr.Expr, w.ctx)
    53  			w.diags = append(w.diags, diags...)
    54  
    55  			m.SetMapIndex(k, reflect.ValueOf(c))
    56  		}
    57  	}
    58  	return nil
    59  }
    60  
    61  func (w *walker) MapElem(m, k, v reflect.Value) error {
    62  	return nil
    63  }
    64  func decodeInterface(expr hcl.Expression, ctx *hcl.EvalContext) (interface{}, hcl.Diagnostics) {
    65  	srvVal, diags := expr.Value(ctx)
    66  
    67  	dst, err := interfaceFromCtyValue(srvVal)
    68  	if err != nil {
    69  		diags = append(diags, &hcl.Diagnostic{
    70  			Severity: hcl.DiagError,
    71  			Summary:  "unsuitable value type",
    72  			Detail:   fmt.Sprintf("Unsuitable value: %s", err.Error()),
    73  			Subject:  expr.StartRange().Ptr(),
    74  			Context:  expr.Range().Ptr(),
    75  		})
    76  	}
    77  
    78  	return dst, diags
    79  }
    80  
    81  func interfaceFromCtyValue(val cty.Value) (interface{}, error) {
    82  	t := val.Type()
    83  
    84  	if val.IsNull() {
    85  		return nil, nil
    86  	}
    87  
    88  	if !val.IsKnown() {
    89  		return nil, fmt.Errorf("value is not known")
    90  	}
    91  
    92  	// The caller should've guaranteed that the given val is conformant with
    93  	// the given type t, so we'll proceed under that assumption here.
    94  
    95  	switch {
    96  	case t.IsPrimitiveType():
    97  		switch t {
    98  		case cty.String:
    99  			return val.AsString(), nil
   100  		case cty.Number:
   101  			if val.RawEquals(cty.PositiveInfinity) {
   102  				return math.Inf(1), nil
   103  			} else if val.RawEquals(cty.NegativeInfinity) {
   104  				return math.Inf(-1), nil
   105  			} else {
   106  				return smallestNumber(val.AsBigFloat()), nil
   107  			}
   108  		case cty.Bool:
   109  			return val.True(), nil
   110  		default:
   111  			panic("unsupported primitive type")
   112  		}
   113  	case isCollectionOfMaps(t):
   114  		result := []map[string]interface{}{}
   115  
   116  		it := val.ElementIterator()
   117  		for it.Next() {
   118  			_, ev := it.Element()
   119  			evi, err := interfaceFromCtyValue(ev)
   120  			if err != nil {
   121  				return nil, err
   122  			}
   123  			result = append(result, evi.(map[string]interface{}))
   124  		}
   125  		return result, nil
   126  	case t.IsListType(), t.IsSetType(), t.IsTupleType():
   127  		result := []interface{}{}
   128  
   129  		it := val.ElementIterator()
   130  		for it.Next() {
   131  			_, ev := it.Element()
   132  			evi, err := interfaceFromCtyValue(ev)
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			result = append(result, evi)
   137  		}
   138  		return result, nil
   139  	case t.IsMapType():
   140  		result := map[string]interface{}{}
   141  		it := val.ElementIterator()
   142  		for it.Next() {
   143  			ek, ev := it.Element()
   144  
   145  			ekv := ek.AsString()
   146  			evv, err := interfaceFromCtyValue(ev)
   147  			if err != nil {
   148  				return nil, err
   149  			}
   150  
   151  			result[ekv] = evv
   152  		}
   153  		return result, nil
   154  	case t.IsObjectType():
   155  		result := map[string]interface{}{}
   156  
   157  		for k := range t.AttributeTypes() {
   158  			av := val.GetAttr(k)
   159  			avv, err := interfaceFromCtyValue(av)
   160  			if err != nil {
   161  				return nil, err
   162  			}
   163  
   164  			result[k] = avv
   165  		}
   166  		return result, nil
   167  	case t.IsCapsuleType():
   168  		rawVal := val.EncapsulatedValue()
   169  		return rawVal, nil
   170  	default:
   171  		// should never happen
   172  		return nil, fmt.Errorf("cannot serialize %s", t.FriendlyName())
   173  	}
   174  }
   175  
   176  func isCollectionOfMaps(t cty.Type) bool {
   177  	switch {
   178  	case t.IsCollectionType():
   179  		et := t.ElementType()
   180  		return et.IsMapType() || et.IsObjectType()
   181  	case t.IsTupleType():
   182  		ets := t.TupleElementTypes()
   183  		for _, et := range ets {
   184  			if !et.IsMapType() && !et.IsObjectType() {
   185  				return false
   186  			}
   187  		}
   188  
   189  		return len(ets) > 0
   190  	default:
   191  		return false
   192  	}
   193  }
   194  
   195  func smallestNumber(b *big.Float) interface{} {
   196  	if v, acc := b.Int64(); acc == big.Exact {
   197  		// check if it fits in int
   198  		if int64(int(v)) == v {
   199  			return int(v)
   200  		}
   201  		return v
   202  	}
   203  
   204  	v, _ := b.Float64()
   205  	return v
   206  }