]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package gocty |
2 | ||
3 | import ( | |
4 | "math/big" | |
5 | "reflect" | |
6 | ||
7 | "github.com/zclconf/go-cty/cty" | |
107c1cdb | 8 | "github.com/zclconf/go-cty/cty/convert" |
15c0b25d AP |
9 | "github.com/zclconf/go-cty/cty/set" |
10 | ) | |
11 | ||
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. | |
14 | // | |
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. | |
20 | // | |
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 | |
30 | // the given Type is. | |
31 | path := make(cty.Path, 0) | |
32 | return toCtyValue(reflect.ValueOf(val), ty, path) | |
33 | } | |
34 | ||
35 | func toCtyValue(val reflect.Value, ty cty.Type, path cty.Path) (cty.Value, error) { | |
107c1cdb ND |
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) | |
40 | } | |
15c0b25d AP |
41 | |
42 | switch ty { | |
43 | case cty.Bool: | |
44 | return toCtyBool(val, path) | |
45 | case cty.Number: | |
46 | return toCtyNumber(val, path) | |
47 | case cty.String: | |
48 | return toCtyString(val, path) | |
49 | case cty.DynamicPseudoType: | |
50 | return toCtyDynamic(val, path) | |
51 | } | |
52 | ||
53 | switch { | |
54 | case ty.IsListType(): | |
55 | return toCtyList(val, ty.ElementType(), path) | |
56 | case ty.IsMapType(): | |
57 | return toCtyMap(val, ty.ElementType(), path) | |
58 | case ty.IsSetType(): | |
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) | |
66 | } | |
67 | ||
68 | // We should never fall out here | |
69 | return cty.NilVal, path.NewErrorf("unsupported target type %#v", ty) | |
70 | } | |
71 | ||
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 | |
75 | } | |
76 | ||
77 | switch val.Kind() { | |
78 | ||
79 | case reflect.Bool: | |
80 | return cty.BoolVal(val.Bool()), nil | |
81 | ||
82 | default: | |
83 | return cty.NilVal, path.NewErrorf("can't convert Go %s to bool", val.Kind()) | |
84 | ||
85 | } | |
86 | ||
87 | } | |
88 | ||
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 | |
92 | } | |
93 | ||
94 | switch val.Kind() { | |
95 | ||
96 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
97 | return cty.NumberIntVal(val.Int()), nil | |
98 | ||
99 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |
100 | return cty.NumberUIntVal(val.Uint()), nil | |
101 | ||
102 | case reflect.Float32, reflect.Float64: | |
103 | return cty.NumberFloatVal(val.Float()), nil | |
104 | ||
105 | case reflect.Struct: | |
106 | if val.Type().AssignableTo(bigIntType) { | |
107 | bigInt := val.Interface().(big.Int) | |
108 | bigFloat := (&big.Float{}).SetInt(&bigInt) | |
109 | val = reflect.ValueOf(*bigFloat) | |
110 | } | |
111 | ||
112 | if val.Type().AssignableTo(bigFloatType) { | |
113 | bigFloat := val.Interface().(big.Float) | |
114 | return cty.NumberVal(&bigFloat), nil | |
115 | } | |
116 | ||
117 | fallthrough | |
118 | default: | |
119 | return cty.NilVal, path.NewErrorf("can't convert Go %s to number", val.Kind()) | |
120 | ||
121 | } | |
122 | ||
123 | } | |
124 | ||
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 | |
128 | } | |
129 | ||
130 | switch val.Kind() { | |
131 | ||
132 | case reflect.String: | |
133 | return cty.StringVal(val.String()), nil | |
134 | ||
135 | default: | |
136 | return cty.NilVal, path.NewErrorf("can't convert Go %s to string", val.Kind()) | |
137 | ||
138 | } | |
139 | ||
140 | } | |
141 | ||
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 | |
145 | } | |
146 | ||
147 | switch val.Kind() { | |
148 | ||
149 | case reflect.Slice: | |
150 | if val.IsNil() { | |
151 | return cty.NullVal(cty.List(ety)), nil | |
152 | } | |
153 | fallthrough | |
154 | case reflect.Array: | |
155 | if val.Len() == 0 { | |
156 | return cty.ListValEmpty(ety), nil | |
157 | } | |
158 | ||
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)) | |
162 | ||
163 | vals := make([]cty.Value, val.Len()) | |
164 | for i := range vals { | |
165 | var err error | |
166 | path[len(path)-1] = cty.IndexStep{ | |
167 | Key: cty.NumberIntVal(int64(i)), | |
168 | } | |
169 | vals[i], err = toCtyValue(val.Index(i), ety, path) | |
170 | if err != nil { | |
171 | return cty.NilVal, err | |
172 | } | |
173 | } | |
174 | ||
175 | // Discard our extra path segment, retaining it as extra capacity | |
176 | // for future appending to the path. | |
177 | path = path[:len(path)-1] | |
178 | ||
179 | return cty.ListVal(vals), nil | |
180 | ||
181 | default: | |
182 | return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.List(ety)) | |
183 | ||
184 | } | |
185 | } | |
186 | ||
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 | |
190 | } | |
191 | ||
192 | switch val.Kind() { | |
193 | ||
194 | case reflect.Map: | |
195 | if val.IsNil() { | |
196 | return cty.NullVal(cty.Map(ety)), nil | |
197 | } | |
198 | ||
199 | if val.Len() == 0 { | |
200 | return cty.MapValEmpty(ety), nil | |
201 | } | |
202 | ||
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) | |
206 | } | |
207 | ||
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)) | |
211 | ||
212 | vals := make(map[string]cty.Value, val.Len()) | |
213 | for _, kv := range val.MapKeys() { | |
214 | k := kv.String() | |
215 | var err error | |
216 | path[len(path)-1] = cty.IndexStep{ | |
217 | Key: cty.StringVal(k), | |
218 | } | |
219 | vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), ety, path) | |
220 | if err != nil { | |
221 | return cty.NilVal, err | |
222 | } | |
223 | } | |
224 | ||
225 | // Discard our extra path segment, retaining it as extra capacity | |
226 | // for future appending to the path. | |
227 | path = path[:len(path)-1] | |
228 | ||
229 | return cty.MapVal(vals), nil | |
230 | ||
231 | default: | |
232 | return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Map(ety)) | |
233 | ||
234 | } | |
235 | } | |
236 | ||
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 | |
240 | } | |
241 | ||
242 | var vals []cty.Value | |
243 | ||
244 | switch val.Kind() { | |
245 | ||
246 | case reflect.Slice: | |
247 | if val.IsNil() { | |
248 | return cty.NullVal(cty.Set(ety)), nil | |
249 | } | |
250 | fallthrough | |
251 | case reflect.Array: | |
252 | if val.Len() == 0 { | |
253 | return cty.SetValEmpty(ety), nil | |
254 | } | |
255 | ||
256 | vals = make([]cty.Value, val.Len()) | |
257 | for i := range vals { | |
258 | var err error | |
259 | vals[i], err = toCtyValue(val.Index(i), ety, path) | |
260 | if err != nil { | |
261 | return cty.NilVal, err | |
262 | } | |
263 | } | |
264 | ||
265 | case reflect.Struct: | |
266 | ||
267 | if !val.Type().AssignableTo(setType) { | |
268 | return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Type(), cty.Set(ety)) | |
269 | } | |
270 | ||
271 | rawSet := val.Interface().(set.Set) | |
272 | inVals := rawSet.Values() | |
273 | ||
274 | if len(inVals) == 0 { | |
275 | return cty.SetValEmpty(ety), nil | |
276 | } | |
277 | ||
278 | vals = make([]cty.Value, len(inVals)) | |
279 | for i := range inVals { | |
280 | var err error | |
281 | vals[i], err = toCtyValue(reflect.ValueOf(inVals[i]), ety, path) | |
282 | if err != nil { | |
283 | return cty.NilVal, err | |
284 | } | |
285 | } | |
286 | ||
287 | default: | |
288 | return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Set(ety)) | |
289 | ||
290 | } | |
291 | ||
292 | return cty.SetVal(vals), nil | |
293 | } | |
294 | ||
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 | |
298 | } | |
299 | ||
300 | switch val.Kind() { | |
301 | ||
302 | case reflect.Map: | |
303 | if val.IsNil() { | |
304 | return cty.NullVal(cty.Object(attrTypes)), nil | |
305 | } | |
306 | ||
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) | |
310 | } | |
311 | ||
312 | if len(attrTypes) == 0 { | |
313 | return cty.EmptyObjectVal, nil | |
314 | } | |
315 | ||
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)) | |
319 | ||
320 | haveKeys := make(map[string]struct{}, val.Len()) | |
321 | for _, kv := range val.MapKeys() { | |
322 | haveKeys[kv.String()] = struct{}{} | |
323 | } | |
324 | ||
325 | vals := make(map[string]cty.Value, len(attrTypes)) | |
326 | for k, at := range attrTypes { | |
327 | var err error | |
328 | path[len(path)-1] = cty.GetAttrStep{ | |
329 | Name: k, | |
330 | } | |
331 | ||
332 | if _, have := haveKeys[k]; !have { | |
333 | vals[k] = cty.NullVal(at) | |
334 | continue | |
335 | } | |
336 | ||
337 | vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), at, path) | |
338 | if err != nil { | |
339 | return cty.NilVal, err | |
340 | } | |
341 | } | |
342 | ||
343 | // Discard our extra path segment, retaining it as extra capacity | |
344 | // for future appending to the path. | |
345 | path = path[:len(path)-1] | |
346 | ||
347 | return cty.ObjectVal(vals), nil | |
348 | ||
349 | case reflect.Struct: | |
350 | if len(attrTypes) == 0 { | |
351 | return cty.EmptyObjectVal, nil | |
352 | } | |
353 | ||
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)) | |
357 | ||
358 | attrFields := structTagIndices(val.Type()) | |
359 | ||
360 | vals := make(map[string]cty.Value, len(attrTypes)) | |
361 | for k, at := range attrTypes { | |
362 | path[len(path)-1] = cty.GetAttrStep{ | |
363 | Name: k, | |
364 | } | |
365 | ||
366 | if fieldIdx, have := attrFields[k]; have { | |
367 | var err error | |
368 | vals[k], err = toCtyValue(val.Field(fieldIdx), at, path) | |
369 | if err != nil { | |
370 | return cty.NilVal, err | |
371 | } | |
372 | } else { | |
373 | vals[k] = cty.NullVal(at) | |
374 | } | |
375 | } | |
376 | ||
377 | // Discard our extra path segment, retaining it as extra capacity | |
378 | // for future appending to the path. | |
379 | path = path[:len(path)-1] | |
380 | ||
381 | return cty.ObjectVal(vals), nil | |
382 | ||
383 | default: | |
384 | return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Object(attrTypes)) | |
385 | ||
386 | } | |
387 | } | |
388 | ||
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 | |
392 | } | |
393 | ||
394 | switch val.Kind() { | |
395 | ||
396 | case reflect.Slice: | |
397 | if val.IsNil() { | |
398 | return cty.NullVal(cty.Tuple(elemTypes)), nil | |
399 | } | |
400 | ||
401 | if val.Len() != len(elemTypes) { | |
402 | return cty.NilVal, path.NewErrorf("wrong number of elements %d; need %d", val.Len(), len(elemTypes)) | |
403 | } | |
404 | ||
405 | if len(elemTypes) == 0 { | |
406 | return cty.EmptyTupleVal, nil | |
407 | } | |
408 | ||
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)) | |
412 | ||
413 | vals := make([]cty.Value, len(elemTypes)) | |
414 | for i, ety := range elemTypes { | |
415 | var err error | |
416 | ||
417 | path[len(path)-1] = cty.IndexStep{ | |
418 | Key: cty.NumberIntVal(int64(i)), | |
419 | } | |
420 | ||
421 | vals[i], err = toCtyValue(val.Index(i), ety, path) | |
422 | if err != nil { | |
423 | return cty.NilVal, err | |
424 | } | |
425 | } | |
426 | ||
427 | // Discard our extra path segment, retaining it as extra capacity | |
428 | // for future appending to the path. | |
429 | path = path[:len(path)-1] | |
430 | ||
431 | return cty.TupleVal(vals), nil | |
432 | ||
433 | case reflect.Struct: | |
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)) | |
437 | } | |
438 | ||
439 | if len(elemTypes) == 0 { | |
440 | return cty.EmptyTupleVal, nil | |
441 | } | |
442 | ||
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)) | |
446 | ||
447 | vals := make([]cty.Value, len(elemTypes)) | |
448 | for i, ety := range elemTypes { | |
449 | var err error | |
450 | ||
451 | path[len(path)-1] = cty.IndexStep{ | |
452 | Key: cty.NumberIntVal(int64(i)), | |
453 | } | |
454 | ||
455 | vals[i], err = toCtyValue(val.Field(i), ety, path) | |
456 | if err != nil { | |
457 | return cty.NilVal, err | |
458 | } | |
459 | } | |
460 | ||
461 | // Discard our extra path segment, retaining it as extra capacity | |
462 | // for future appending to the path. | |
463 | path = path[:len(path)-1] | |
464 | ||
465 | return cty.TupleVal(vals), nil | |
466 | ||
467 | default: | |
468 | return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Tuple(elemTypes)) | |
469 | ||
470 | } | |
471 | } | |
472 | ||
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 | |
476 | } | |
477 | ||
478 | if val.Kind() != reflect.Ptr { | |
479 | if !val.CanAddr() { | |
480 | return cty.NilVal, path.NewErrorf("source value for capsule %#v must be addressable", capsuleType) | |
481 | } | |
482 | ||
483 | val = val.Addr() | |
484 | } | |
485 | ||
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) | |
488 | } | |
489 | ||
490 | return cty.CapsuleVal(capsuleType, val.Interface()), nil | |
491 | } | |
492 | ||
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 | |
496 | } | |
497 | ||
498 | switch val.Kind() { | |
499 | ||
500 | case reflect.Struct: | |
501 | if !val.Type().AssignableTo(valueType) { | |
502 | return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Type()) | |
503 | } | |
504 | ||
505 | return val.Interface().(cty.Value), nil | |
506 | ||
507 | default: | |
508 | return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Kind()) | |
509 | ||
510 | } | |
511 | ||
512 | } | |
513 | ||
107c1cdb ND |
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 | |
517 | } | |
518 | ||
519 | givenVal := wrappedVal.Interface().(cty.Value) | |
520 | ||
521 | val, err := convert.Convert(givenVal, wantTy) | |
522 | if err != nil { | |
523 | return cty.NilVal, path.NewErrorf("unsuitable value: %s", err) | |
524 | } | |
525 | return val, nil | |
526 | } | |
527 | ||
15c0b25d AP |
528 | // toCtyUnwrapPointer is a helper for dealing with Go pointers. It has three |
529 | // possible outcomes: | |
530 | // | |
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. | |
535 | // | |
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 { | |
540 | if val.IsNil() { | |
541 | return reflect.Value{} | |
542 | } | |
543 | ||
544 | val = val.Elem() | |
545 | } | |
546 | ||
547 | return val | |
548 | } |