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 }