6 "github.com/zclconf/go-cty/cty"
7 "github.com/zclconf/go-cty/cty/convert"
10 // CoerceValue attempts to force the given value to conform to the type
11 // implied by the receiever, while also applying the same validation and
12 // transformation rules that would be applied by the decoder specification
13 // returned by method DecoderSpec.
15 // This is useful in situations where a configuration must be derived from
16 // an already-decoded value. It is always better to decode directly from
17 // configuration where possible since then source location information is
18 // still available to produce diagnostics, but in special situations this
19 // function allows a compatible result to be obtained even if the
20 // configuration objects are not available.
22 // If the given value cannot be converted to conform to the receiving schema
23 // then an error is returned describing one of possibly many problems. This
24 // error may be a cty.PathError indicating a position within the nested
25 // data structure where the problem applies.
26 func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
28 return b.coerceValue(in, path)
31 func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
34 return cty.NullVal(b.ImpliedType()), nil
36 return cty.UnknownVal(b.ImpliedType()), nil
40 if !ty.IsObjectType() {
41 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
44 for name := range ty.AttributeTypes() {
45 if _, defined := b.Attributes[name]; defined {
48 if _, defined := b.BlockTypes[name]; defined {
51 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
54 attrs := make(map[string]cty.Value)
56 for name, attrS := range b.Attributes {
59 case ty.HasAttribute(name):
60 val = in.GetAttr(name)
61 case attrS.Computed || attrS.Optional:
62 val = cty.NullVal(attrS.Type)
64 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
67 val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
69 return cty.UnknownVal(b.ImpliedType()), err
74 for typeName, blockS := range b.BlockTypes {
75 switch blockS.Nesting {
77 case NestingSingle, NestingGroup:
79 case ty.HasAttribute(typeName):
81 val := in.GetAttr(typeName)
82 attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
84 return cty.UnknownVal(b.ImpliedType()), err
86 case blockS.MinItems != 1 && blockS.MaxItems != 1:
87 if blockS.Nesting == NestingGroup {
88 attrs[typeName] = blockS.EmptyValue()
90 attrs[typeName] = cty.NullVal(blockS.ImpliedType())
93 // We use the word "attribute" here because we're talking about
94 // the cty sense of that word rather than the HCL sense.
95 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
100 case ty.HasAttribute(typeName):
101 coll := in.GetAttr(typeName)
105 attrs[typeName] = cty.NullVal(cty.List(blockS.ImpliedType()))
107 case !coll.IsKnown():
108 attrs[typeName] = cty.UnknownVal(cty.List(blockS.ImpliedType()))
112 if !coll.CanIterateElements() {
113 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
115 l := coll.LengthInt()
116 if l < blockS.MinItems {
117 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
119 if l > blockS.MaxItems && blockS.MaxItems > 0 {
120 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("too many items for attribute %q; cannot have more than %d", typeName, blockS.MaxItems)
123 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
126 elems := make([]cty.Value, 0, l)
128 path = append(path, cty.GetAttrStep{Name: typeName})
129 for it := coll.ElementIterator(); it.Next(); {
131 idx, val := it.Element()
132 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
134 return cty.UnknownVal(b.ImpliedType()), err
136 elems = append(elems, val)
139 attrs[typeName] = cty.ListVal(elems)
140 case blockS.MinItems == 0:
141 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
143 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
148 case ty.HasAttribute(typeName):
149 coll := in.GetAttr(typeName)
153 attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType()))
155 case !coll.IsKnown():
156 attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType()))
160 if !coll.CanIterateElements() {
161 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
163 l := coll.LengthInt()
164 if l < blockS.MinItems {
165 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
167 if l > blockS.MaxItems && blockS.MaxItems > 0 {
168 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("too many items for attribute %q; cannot have more than %d", typeName, blockS.MaxItems)
171 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
174 elems := make([]cty.Value, 0, l)
176 path = append(path, cty.GetAttrStep{Name: typeName})
177 for it := coll.ElementIterator(); it.Next(); {
179 idx, val := it.Element()
180 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
182 return cty.UnknownVal(b.ImpliedType()), err
184 elems = append(elems, val)
187 attrs[typeName] = cty.SetVal(elems)
188 case blockS.MinItems == 0:
189 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
191 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
196 case ty.HasAttribute(typeName):
197 coll := in.GetAttr(typeName)
201 attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType()))
203 case !coll.IsKnown():
204 attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType()))
208 if !coll.CanIterateElements() {
209 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
211 l := coll.LengthInt()
213 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
216 elems := make(map[string]cty.Value)
218 path = append(path, cty.GetAttrStep{Name: typeName})
219 for it := coll.ElementIterator(); it.Next(); {
221 key, val := it.Element()
222 if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
223 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
225 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
227 return cty.UnknownVal(b.ImpliedType()), err
229 elems[key.AsString()] = val
233 // If the attribute values here contain any DynamicPseudoTypes,
234 // the concrete type must be an object.
237 case coll.Type().IsObjectType():
240 // It's possible that we were given a map, and need to coerce it to an object
241 ety := coll.Type().ElementType()
242 for _, v := range elems {
243 if !v.Type().Equals(ety) {
251 attrs[typeName] = cty.ObjectVal(elems)
253 attrs[typeName] = cty.MapVal(elems)
256 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
260 // should never happen because above is exhaustive
261 panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting))
265 return cty.ObjectVal(attrs), nil
268 func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
269 val, err := convert.Convert(in, a.Type)
271 return cty.UnknownVal(a.Type), path.NewError(err)