7 "github.com/zclconf/go-cty/cty"
8 "github.com/zclconf/go-cty/cty/convert"
9 "github.com/zclconf/go-cty/cty/set"
12 // ToCtyValue produces a cty.Value from a Go value. The result will conform
13 // to the given type, or an error will be returned if this is not possible.
15 // The target type serves as a hint to resolve ambiguities in the mapping.
16 // For example, the Go type set.Set tells us that the value is a set but
17 // does not describe the set's element type. This also allows for convenient
18 // conversions, such as populating a set from a slice rather than having to
19 // first explicitly instantiate a set.Set.
21 // The audience of this function is assumed to be the developers of Go code
22 // that is integrating with cty, and thus the error messages it returns are
23 // presented from Go's perspective. These messages are thus not appropriate
24 // for display to end-users. An error returned from ToCtyValue represents a
25 // bug in the calling program, not user error.
26 func ToCtyValue(val interface{}, ty cty.Type) (cty.Value, error) {
27 // 'path' starts off as empty but will grow for each level of recursive
28 // call we make, so by the time toCtyValue returns it is likely to have
29 // unused capacity on the end of it, depending on how deeply-recursive
31 path := make(cty.Path, 0)
32 return toCtyValue(reflect.ValueOf(val), ty, path)
35 func toCtyValue(val reflect.Value, ty cty.Type, path cty.Path) (cty.Value, error) {
36 if val != (reflect.Value{}) && val.Type().AssignableTo(valueType) {
37 // If the source value is a cty.Value then we'll try to just pass
38 // through to the target type directly.
39 return toCtyPassthrough(val, ty, path)
44 return toCtyBool(val, path)
46 return toCtyNumber(val, path)
48 return toCtyString(val, path)
49 case cty.DynamicPseudoType:
50 return toCtyDynamic(val, path)
55 return toCtyList(val, ty.ElementType(), path)
57 return toCtyMap(val, ty.ElementType(), path)
59 return toCtySet(val, ty.ElementType(), path)
60 case ty.IsObjectType():
61 return toCtyObject(val, ty.AttributeTypes(), path)
62 case ty.IsTupleType():
63 return toCtyTuple(val, ty.TupleElementTypes(), path)
64 case ty.IsCapsuleType():
65 return toCtyCapsule(val, ty, path)
68 // We should never fall out here
69 return cty.NilVal, path.NewErrorf("unsupported target type %#v", ty)
72 func toCtyBool(val reflect.Value, path cty.Path) (cty.Value, error) {
73 if val = toCtyUnwrapPointer(val); !val.IsValid() {
74 return cty.NullVal(cty.Bool), nil
80 return cty.BoolVal(val.Bool()), nil
83 return cty.NilVal, path.NewErrorf("can't convert Go %s to bool", val.Kind())
89 func toCtyNumber(val reflect.Value, path cty.Path) (cty.Value, error) {
90 if val = toCtyUnwrapPointer(val); !val.IsValid() {
91 return cty.NullVal(cty.Number), nil
96 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
97 return cty.NumberIntVal(val.Int()), nil
99 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
100 return cty.NumberUIntVal(val.Uint()), nil
102 case reflect.Float32, reflect.Float64:
103 return cty.NumberFloatVal(val.Float()), nil
106 if val.Type().AssignableTo(bigIntType) {
107 bigInt := val.Interface().(big.Int)
108 bigFloat := (&big.Float{}).SetInt(&bigInt)
109 val = reflect.ValueOf(*bigFloat)
112 if val.Type().AssignableTo(bigFloatType) {
113 bigFloat := val.Interface().(big.Float)
114 return cty.NumberVal(&bigFloat), nil
119 return cty.NilVal, path.NewErrorf("can't convert Go %s to number", val.Kind())
125 func toCtyString(val reflect.Value, path cty.Path) (cty.Value, error) {
126 if val = toCtyUnwrapPointer(val); !val.IsValid() {
127 return cty.NullVal(cty.String), nil
133 return cty.StringVal(val.String()), nil
136 return cty.NilVal, path.NewErrorf("can't convert Go %s to string", val.Kind())
142 func toCtyList(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) {
143 if val = toCtyUnwrapPointer(val); !val.IsValid() {
144 return cty.NullVal(cty.List(ety)), nil
151 return cty.NullVal(cty.List(ety)), nil
156 return cty.ListValEmpty(ety), nil
159 // While we work on our elements we'll temporarily grow
160 // path to give us a place to put our index step.
161 path = append(path, cty.PathStep(nil))
163 vals := make([]cty.Value, val.Len())
164 for i := range vals {
166 path[len(path)-1] = cty.IndexStep{
167 Key: cty.NumberIntVal(int64(i)),
169 vals[i], err = toCtyValue(val.Index(i), ety, path)
171 return cty.NilVal, err
175 // Discard our extra path segment, retaining it as extra capacity
176 // for future appending to the path.
177 path = path[:len(path)-1]
179 return cty.ListVal(vals), nil
182 return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.List(ety))
187 func toCtyMap(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) {
188 if val = toCtyUnwrapPointer(val); !val.IsValid() {
189 return cty.NullVal(cty.Map(ety)), nil
196 return cty.NullVal(cty.Map(ety)), nil
200 return cty.MapValEmpty(ety), nil
203 keyType := val.Type().Key()
204 if keyType.Kind() != reflect.String {
205 return cty.NilVal, path.NewErrorf("can't convert Go map with key type %s; key type must be string", keyType)
208 // While we work on our elements we'll temporarily grow
209 // path to give us a place to put our index step.
210 path = append(path, cty.PathStep(nil))
212 vals := make(map[string]cty.Value, val.Len())
213 for _, kv := range val.MapKeys() {
216 path[len(path)-1] = cty.IndexStep{
217 Key: cty.StringVal(k),
219 vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), ety, path)
221 return cty.NilVal, err
225 // Discard our extra path segment, retaining it as extra capacity
226 // for future appending to the path.
227 path = path[:len(path)-1]
229 return cty.MapVal(vals), nil
232 return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Map(ety))
237 func toCtySet(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) {
238 if val = toCtyUnwrapPointer(val); !val.IsValid() {
239 return cty.NullVal(cty.Set(ety)), nil
248 return cty.NullVal(cty.Set(ety)), nil
253 return cty.SetValEmpty(ety), nil
256 vals = make([]cty.Value, val.Len())
257 for i := range vals {
259 vals[i], err = toCtyValue(val.Index(i), ety, path)
261 return cty.NilVal, err
267 if !val.Type().AssignableTo(setType) {
268 return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Type(), cty.Set(ety))
271 rawSet := val.Interface().(set.Set)
272 inVals := rawSet.Values()
274 if len(inVals) == 0 {
275 return cty.SetValEmpty(ety), nil
278 vals = make([]cty.Value, len(inVals))
279 for i := range inVals {
281 vals[i], err = toCtyValue(reflect.ValueOf(inVals[i]), ety, path)
283 return cty.NilVal, err
288 return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Set(ety))
292 return cty.SetVal(vals), nil
295 func toCtyObject(val reflect.Value, attrTypes map[string]cty.Type, path cty.Path) (cty.Value, error) {
296 if val = toCtyUnwrapPointer(val); !val.IsValid() {
297 return cty.NullVal(cty.Object(attrTypes)), nil
304 return cty.NullVal(cty.Object(attrTypes)), nil
307 keyType := val.Type().Key()
308 if keyType.Kind() != reflect.String {
309 return cty.NilVal, path.NewErrorf("can't convert Go map with key type %s; key type must be string", keyType)
312 if len(attrTypes) == 0 {
313 return cty.EmptyObjectVal, nil
316 // While we work on our elements we'll temporarily grow
317 // path to give us a place to put our GetAttr step.
318 path = append(path, cty.PathStep(nil))
320 haveKeys := make(map[string]struct{}, val.Len())
321 for _, kv := range val.MapKeys() {
322 haveKeys[kv.String()] = struct{}{}
325 vals := make(map[string]cty.Value, len(attrTypes))
326 for k, at := range attrTypes {
328 path[len(path)-1] = cty.GetAttrStep{
332 if _, have := haveKeys[k]; !have {
333 vals[k] = cty.NullVal(at)
337 vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), at, path)
339 return cty.NilVal, err
343 // Discard our extra path segment, retaining it as extra capacity
344 // for future appending to the path.
345 path = path[:len(path)-1]
347 return cty.ObjectVal(vals), nil
350 if len(attrTypes) == 0 {
351 return cty.EmptyObjectVal, nil
354 // While we work on our elements we'll temporarily grow
355 // path to give us a place to put our GetAttr step.
356 path = append(path, cty.PathStep(nil))
358 attrFields := structTagIndices(val.Type())
360 vals := make(map[string]cty.Value, len(attrTypes))
361 for k, at := range attrTypes {
362 path[len(path)-1] = cty.GetAttrStep{
366 if fieldIdx, have := attrFields[k]; have {
368 vals[k], err = toCtyValue(val.Field(fieldIdx), at, path)
370 return cty.NilVal, err
373 vals[k] = cty.NullVal(at)
377 // Discard our extra path segment, retaining it as extra capacity
378 // for future appending to the path.
379 path = path[:len(path)-1]
381 return cty.ObjectVal(vals), nil
384 return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Object(attrTypes))
389 func toCtyTuple(val reflect.Value, elemTypes []cty.Type, path cty.Path) (cty.Value, error) {
390 if val = toCtyUnwrapPointer(val); !val.IsValid() {
391 return cty.NullVal(cty.Tuple(elemTypes)), nil
398 return cty.NullVal(cty.Tuple(elemTypes)), nil
401 if val.Len() != len(elemTypes) {
402 return cty.NilVal, path.NewErrorf("wrong number of elements %d; need %d", val.Len(), len(elemTypes))
405 if len(elemTypes) == 0 {
406 return cty.EmptyTupleVal, nil
409 // While we work on our elements we'll temporarily grow
410 // path to give us a place to put our Index step.
411 path = append(path, cty.PathStep(nil))
413 vals := make([]cty.Value, len(elemTypes))
414 for i, ety := range elemTypes {
417 path[len(path)-1] = cty.IndexStep{
418 Key: cty.NumberIntVal(int64(i)),
421 vals[i], err = toCtyValue(val.Index(i), ety, path)
423 return cty.NilVal, err
427 // Discard our extra path segment, retaining it as extra capacity
428 // for future appending to the path.
429 path = path[:len(path)-1]
431 return cty.TupleVal(vals), nil
434 fieldCount := val.Type().NumField()
435 if fieldCount != len(elemTypes) {
436 return cty.NilVal, path.NewErrorf("wrong number of struct fields %d; need %d", fieldCount, len(elemTypes))
439 if len(elemTypes) == 0 {
440 return cty.EmptyTupleVal, nil
443 // While we work on our elements we'll temporarily grow
444 // path to give us a place to put our Index step.
445 path = append(path, cty.PathStep(nil))
447 vals := make([]cty.Value, len(elemTypes))
448 for i, ety := range elemTypes {
451 path[len(path)-1] = cty.IndexStep{
452 Key: cty.NumberIntVal(int64(i)),
455 vals[i], err = toCtyValue(val.Field(i), ety, path)
457 return cty.NilVal, err
461 // Discard our extra path segment, retaining it as extra capacity
462 // for future appending to the path.
463 path = path[:len(path)-1]
465 return cty.TupleVal(vals), nil
468 return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Tuple(elemTypes))
473 func toCtyCapsule(val reflect.Value, capsuleType cty.Type, path cty.Path) (cty.Value, error) {
474 if val = toCtyUnwrapPointer(val); !val.IsValid() {
475 return cty.NullVal(capsuleType), nil
478 if val.Kind() != reflect.Ptr {
480 return cty.NilVal, path.NewErrorf("source value for capsule %#v must be addressable", capsuleType)
486 if !val.Type().Elem().AssignableTo(capsuleType.EncapsulatedType()) {
487 return cty.NilVal, path.NewErrorf("value of type %T not compatible with capsule %#v", val.Interface(), capsuleType)
490 return cty.CapsuleVal(capsuleType, val.Interface()), nil
493 func toCtyDynamic(val reflect.Value, path cty.Path) (cty.Value, error) {
494 if val = toCtyUnwrapPointer(val); !val.IsValid() {
495 return cty.NullVal(cty.DynamicPseudoType), nil
501 if !val.Type().AssignableTo(valueType) {
502 return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Type())
505 return val.Interface().(cty.Value), nil
508 return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Kind())
514 func toCtyPassthrough(wrappedVal reflect.Value, wantTy cty.Type, path cty.Path) (cty.Value, error) {
515 if wrappedVal = toCtyUnwrapPointer(wrappedVal); !wrappedVal.IsValid() {
516 return cty.NullVal(wantTy), nil
519 givenVal := wrappedVal.Interface().(cty.Value)
521 val, err := convert.Convert(givenVal, wantTy)
523 return cty.NilVal, path.NewErrorf("unsuitable value: %s", err)
528 // toCtyUnwrapPointer is a helper for dealing with Go pointers. It has three
529 // possible outcomes:
531 // - Given value isn't a pointer, so it's just returned as-is.
532 // - Given value is a non-nil pointer, in which case it is dereferenced
533 // and the result returned.
534 // - Given value is a nil pointer, in which case an invalid value is returned.
536 // For nested pointer types, like **int, they are all dereferenced in turn
537 // until a non-pointer value is found, or until a nil pointer is encountered.
538 func toCtyUnwrapPointer(val reflect.Value) reflect.Value {
539 for val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
541 return reflect.Value{}