8 "github.com/zclconf/go-cty/cty"
11 // FromCtyValue assigns a cty.Value to a reflect.Value, which must be a pointer,
12 // using a fixed set of conversion rules.
14 // This function considers its audience to be the creator of the cty Value
15 // given, and thus the error messages it generates are (unlike with ToCtyValue)
16 // presented in cty terminology that is generally appropriate to return to
17 // end-users in applications where cty data structures are built from
18 // user-provided configuration. In particular this means that if incorrect
19 // target types are provided by the calling application the resulting error
20 // messages are likely to be confusing, since we assume that the given target
21 // type is correct and the cty.Value is where the error lies.
23 // If an error is returned, the target data structure may have been partially
24 // populated, but the degree to which this is true is an implementation
25 // detail that the calling application should not rely on.
27 // The function will panic if given a non-pointer as the Go value target,
28 // since that is considered to be a bug in the calling program.
29 func FromCtyValue(val cty.Value, target interface{}) error {
30 tVal := reflect.ValueOf(target)
31 if tVal.Kind() != reflect.Ptr {
32 panic("target value is not a pointer")
35 panic("target value is nil pointer")
38 // 'path' starts off as empty but will grow for each level of recursive
39 // call we make, so by the time fromCtyValue returns it is likely to have
40 // unused capacity on the end of it, depending on how deeply-recursive
41 // the given cty.Value is.
42 path := make(cty.Path, 0)
43 return fromCtyValue(val, tVal, path)
46 func fromCtyValue(val cty.Value, target reflect.Value, path cty.Path) error {
49 deepTarget := fromCtyPopulatePtr(target, false)
51 // If we're decoding into a cty.Value then we just pass through the
52 // value as-is, to enable partial decoding. This is the only situation
53 // where unknown values are permitted.
54 if deepTarget.Kind() == reflect.Struct && deepTarget.Type().AssignableTo(valueType) {
55 deepTarget.Set(reflect.ValueOf(val))
59 // Lists and maps can be nil without indirection, but everything else
60 // requires a pointer and we set it immediately to nil.
61 // We also make an exception for capsule types because we want to handle
62 // pointers specially for these.
63 // (fromCtyList and fromCtyMap must therefore deal with val.IsNull, while
64 // other types can assume no nulls after this point.)
65 if val.IsNull() && !val.Type().IsListType() && !val.Type().IsMapType() && !val.Type().IsCapsuleType() {
66 target = fromCtyPopulatePtr(target, true)
67 if target.Kind() != reflect.Ptr {
68 return path.NewErrorf("null value is not allowed")
71 target.Set(reflect.Zero(target.Type()))
78 return path.NewErrorf("value must be known")
83 return fromCtyBool(val, target, path)
85 return fromCtyNumber(val, target, path)
87 return fromCtyString(val, target, path)
92 return fromCtyList(val, target, path)
94 return fromCtyMap(val, target, path)
96 return fromCtySet(val, target, path)
97 case ty.IsObjectType():
98 return fromCtyObject(val, target, path)
99 case ty.IsTupleType():
100 return fromCtyTuple(val, target, path)
101 case ty.IsCapsuleType():
102 return fromCtyCapsule(val, target, path)
105 // We should never fall out here; reaching here indicates a bug in this
107 return path.NewErrorf("unsupported source type %#v", ty)
110 func fromCtyBool(val cty.Value, target reflect.Value, path cty.Path) error {
111 switch target.Kind() {
114 target.SetBool(val.True())
118 return likelyRequiredTypesError(path, target)
123 func fromCtyNumber(val cty.Value, target reflect.Value, path cty.Path) error {
124 bf := val.AsBigFloat()
126 switch target.Kind() {
128 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
129 return fromCtyNumberInt(bf, target, path)
131 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
132 return fromCtyNumberUInt(bf, target, path)
134 case reflect.Float32, reflect.Float64:
135 return fromCtyNumberFloat(bf, target, path)
138 return fromCtyNumberBig(bf, target, path)
141 return likelyRequiredTypesError(path, target)
146 func fromCtyNumberInt(bf *big.Float, target reflect.Value, path cty.Path) error {
147 // Doing this with switch rather than << arithmetic because << with
148 // result >32-bits is not portable to 32-bit systems.
151 switch target.Type().Bits() {
165 panic("weird number of bits in target int")
168 iv, accuracy := bf.Int64()
169 if accuracy != big.Exact || iv < min || iv > max {
170 return path.NewErrorf("value must be a whole number, between %d and %d", min, max)
177 func fromCtyNumberUInt(bf *big.Float, target reflect.Value, path cty.Path) error {
178 // Doing this with switch rather than << arithmetic because << with
179 // result >32-bits is not portable to 32-bit systems.
181 switch target.Type().Bits() {
191 panic("weird number of bits in target uint")
194 iv, accuracy := bf.Uint64()
195 if accuracy != big.Exact || iv > max {
196 return path.NewErrorf("value must be a whole number, between 0 and %d inclusive", max)
203 func fromCtyNumberFloat(bf *big.Float, target reflect.Value, path cty.Path) error {
204 switch target.Kind() {
205 case reflect.Float32, reflect.Float64:
206 fv, accuracy := bf.Float64()
207 if accuracy != big.Exact {
208 // We allow the precision to be truncated as part of our conversion,
209 // but we don't want to silently introduce infinities.
210 if math.IsInf(fv, 0) {
211 return path.NewErrorf("value must be between %f and %f inclusive", -math.MaxFloat64, math.MaxFloat64)
217 panic("unsupported kind of float")
221 func fromCtyNumberBig(bf *big.Float, target reflect.Value, path cty.Path) error {
224 case bigFloatType.ConvertibleTo(target.Type()):
226 target.Set(reflect.ValueOf(bf).Elem().Convert(target.Type()))
229 case bigIntType.ConvertibleTo(target.Type()):
230 bi, accuracy := bf.Int(nil)
231 if accuracy != big.Exact {
232 return path.NewErrorf("value must be a whole number")
234 target.Set(reflect.ValueOf(bi).Elem().Convert(target.Type()))
238 return likelyRequiredTypesError(path, target)
242 func fromCtyString(val cty.Value, target reflect.Value, path cty.Path) error {
243 switch target.Kind() {
245 target.SetString(val.AsString())
249 return likelyRequiredTypesError(path, target)
254 func fromCtyList(val cty.Value, target reflect.Value, path cty.Path) error {
255 switch target.Kind() {
259 target.Set(reflect.Zero(target.Type()))
263 length := val.LengthInt()
264 tv := reflect.MakeSlice(target.Type(), length, length)
266 path = append(path, nil)
270 val.ForEachElement(func(key cty.Value, val cty.Value) bool {
271 path[len(path)-1] = cty.IndexStep{
272 Key: cty.NumberIntVal(int64(i)),
275 targetElem := tv.Index(i)
276 err = fromCtyValue(val, targetElem, path)
288 path = path[:len(path)-1]
295 return path.NewErrorf("null value is not allowed")
298 length := val.LengthInt()
299 if length != target.Len() {
300 return path.NewErrorf("must be a list of length %d", target.Len())
303 path = append(path, nil)
307 val.ForEachElement(func(key cty.Value, val cty.Value) bool {
308 path[len(path)-1] = cty.IndexStep{
309 Key: cty.NumberIntVal(int64(i)),
312 targetElem := target.Index(i)
313 err = fromCtyValue(val, targetElem, path)
325 path = path[:len(path)-1]
330 return likelyRequiredTypesError(path, target)
335 func fromCtyMap(val cty.Value, target reflect.Value, path cty.Path) error {
337 switch target.Kind() {
341 target.Set(reflect.Zero(target.Type()))
345 tv := reflect.MakeMap(target.Type())
346 et := target.Type().Elem()
348 path = append(path, nil)
351 val.ForEachElement(func(key cty.Value, val cty.Value) bool {
352 path[len(path)-1] = cty.IndexStep{
358 targetElem := reflect.New(et)
359 err = fromCtyValue(val, targetElem, path)
361 tv.SetMapIndex(reflect.ValueOf(ks), targetElem.Elem())
369 path = path[:len(path)-1]
375 return likelyRequiredTypesError(path, target)
380 func fromCtySet(val cty.Value, target reflect.Value, path cty.Path) error {
381 switch target.Kind() {
385 target.Set(reflect.Zero(target.Type()))
389 length := val.LengthInt()
390 tv := reflect.MakeSlice(target.Type(), length, length)
394 val.ForEachElement(func(key cty.Value, val cty.Value) bool {
395 targetElem := tv.Index(i)
396 err = fromCtyValue(val, targetElem, path)
413 return path.NewErrorf("null value is not allowed")
416 length := val.LengthInt()
417 if length != target.Len() {
418 return path.NewErrorf("must be a set of length %d", target.Len())
423 val.ForEachElement(func(key cty.Value, val cty.Value) bool {
424 targetElem := target.Index(i)
425 err = fromCtyValue(val, targetElem, path)
439 // TODO: decode into set.Set instance
442 return likelyRequiredTypesError(path, target)
447 func fromCtyObject(val cty.Value, target reflect.Value, path cty.Path) error {
449 switch target.Kind() {
453 attrTypes := val.Type().AttributeTypes()
454 targetFields := structTagIndices(target.Type())
456 path = append(path, nil)
458 for k, i := range targetFields {
459 if _, exists := attrTypes[k]; !exists {
460 // If the field in question isn't able to represent nil,
462 fk := target.Field(i).Kind()
464 case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface:
467 return path.NewErrorf("missing required attribute %q", k)
472 for k := range attrTypes {
473 path[len(path)-1] = cty.GetAttrStep{
477 fieldIdx, exists := targetFields[k]
479 return path.NewErrorf("unsupported attribute %q", k)
484 targetField := target.Field(fieldIdx)
485 err := fromCtyValue(ev, targetField, path)
491 path = path[:len(path)-1]
496 return likelyRequiredTypesError(path, target)
501 func fromCtyTuple(val cty.Value, target reflect.Value, path cty.Path) error {
503 switch target.Kind() {
507 elemTypes := val.Type().TupleElementTypes()
508 fieldCount := target.Type().NumField()
510 if fieldCount != len(elemTypes) {
511 return path.NewErrorf("a tuple of %d elements is required", fieldCount)
514 path = append(path, nil)
516 for i := range elemTypes {
517 path[len(path)-1] = cty.IndexStep{
518 Key: cty.NumberIntVal(int64(i)),
521 ev := val.Index(cty.NumberIntVal(int64(i)))
523 targetField := target.Field(i)
524 err := fromCtyValue(ev, targetField, path)
530 path = path[:len(path)-1]
535 return likelyRequiredTypesError(path, target)
540 func fromCtyCapsule(val cty.Value, target reflect.Value, path cty.Path) error {
542 if target.Kind() == reflect.Ptr {
543 // Walk through indirection until we get to the last pointer,
544 // which we might set to null below.
545 target = fromCtyPopulatePtr(target, true)
548 target.Set(reflect.Zero(target.Type()))
552 // Since a capsule contains a pointer to an object, we'll preserve
553 // that pointer on the way out and thus allow the caller to recover
554 // the original object, rather than a copy of it.
556 eType := val.Type().EncapsulatedType()
558 if !eType.AssignableTo(target.Elem().Type()) {
559 // Our interface contract promises that we won't expose Go
560 // implementation details in error messages, so we need to keep
561 // this vague. This can only arise if a calling application has
562 // more than one capsule type in play and a user mixes them up.
563 return path.NewErrorf("incorrect type %s", val.Type().FriendlyName())
566 target.Set(reflect.ValueOf(val.EncapsulatedValue()))
571 return path.NewErrorf("null value is not allowed")
574 // If our target isn't a pointer then we will attempt to copy
575 // the encapsulated value into it.
577 eType := val.Type().EncapsulatedType()
579 if !eType.AssignableTo(target.Type()) {
580 // Our interface contract promises that we won't expose Go
581 // implementation details in error messages, so we need to keep
582 // this vague. This can only arise if a calling application has
583 // more than one capsule type in play and a user mixes them up.
584 return path.NewErrorf("incorrect type %s", val.Type().FriendlyName())
587 // We know that EncapsulatedValue is always a pointer, so we
588 // can safely call .Elem on its reflect.Value.
589 target.Set(reflect.ValueOf(val.EncapsulatedValue()).Elem())
596 // fromCtyPopulatePtr recognizes when target is a pointer type and allocates
597 // a value to assign to that pointer, which it returns.
599 // If the given value has multiple levels of indirection, like **int, these
600 // will be processed in turn so that the return value is guaranteed to be
603 // As an exception, if decodingNull is true then the returned value will be
604 // the final level of pointer, if any, so that the caller can assign it
605 // as nil to represent a null value. If the given target value is not a pointer
606 // at all then the returned value will be just the given target, so the caller
607 // must test if the returned value is a pointer before trying to assign nil
609 func fromCtyPopulatePtr(target reflect.Value, decodingNull bool) reflect.Value {
611 if target.Kind() == reflect.Interface && !target.IsNil() {
613 if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
618 if target.Kind() != reflect.Ptr {
622 // Stop early if we're decodingNull and we've found our last indirection
623 if target.Elem().Kind() != reflect.Ptr && decodingNull && target.CanSet() {
628 target.Set(reflect.New(target.Type().Elem()))
631 target = target.Elem()
636 // likelyRequiredTypesError returns an error that states which types are
637 // acceptable by making some assumptions about what types we support for
638 // each target Go kind. It's not a precise science but it allows us to return
639 // an error message that is cty-user-oriented rather than Go-oriented.
641 // Generally these error messages should be a matter of last resort, since
642 // the calling application should be validating user-provided value types
643 // before decoding anyway.
644 func likelyRequiredTypesError(path cty.Path, target reflect.Value) error {
645 switch target.Kind() {
648 return path.NewErrorf("bool value is required")
651 return path.NewErrorf("string value is required")
653 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
655 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
657 case reflect.Float32, reflect.Float64:
658 return path.NewErrorf("number value is required")
660 case reflect.Slice, reflect.Array:
661 return path.NewErrorf("list or set value is required")
664 return path.NewErrorf("map or object value is required")
669 case target.Type().AssignableTo(bigFloatType) || target.Type().AssignableTo(bigIntType):
670 return path.NewErrorf("number value is required")
672 case target.Type().AssignableTo(setType):
673 return path.NewErrorf("set or list value is required")
676 return path.NewErrorf("object or tuple value is required")
681 // We should avoid getting into this path, since this error
682 // message is rather useless.
683 return path.NewErrorf("incorrect type")