]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / configs / configschema / coerce_value.go
CommitLineData
107c1cdb
ND
1package configschema
2
3import (
4 "fmt"
5
6 "github.com/zclconf/go-cty/cty"
7 "github.com/zclconf/go-cty/cty/convert"
8)
9
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.
14//
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.
21//
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.
26func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
27 var path cty.Path
28 return b.coerceValue(in, path)
29}
30
31func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
32 switch {
33 case in.IsNull():
34 return cty.NullVal(b.ImpliedType()), nil
35 case !in.IsKnown():
36 return cty.UnknownVal(b.ImpliedType()), nil
37 }
38
39 ty := in.Type()
40 if !ty.IsObjectType() {
41 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
42 }
43
44 for name := range ty.AttributeTypes() {
45 if _, defined := b.Attributes[name]; defined {
46 continue
47 }
48 if _, defined := b.BlockTypes[name]; defined {
49 continue
50 }
51 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
52 }
53
54 attrs := make(map[string]cty.Value)
55
56 for name, attrS := range b.Attributes {
57 var val cty.Value
58 switch {
59 case ty.HasAttribute(name):
60 val = in.GetAttr(name)
61 case attrS.Computed || attrS.Optional:
62 val = cty.NullVal(attrS.Type)
63 default:
64 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
65 }
66
67 val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
68 if err != nil {
69 return cty.UnknownVal(b.ImpliedType()), err
70 }
71
72 attrs[name] = val
73 }
74 for typeName, blockS := range b.BlockTypes {
75 switch blockS.Nesting {
76
77 case NestingSingle, NestingGroup:
78 switch {
79 case ty.HasAttribute(typeName):
80 var err error
81 val := in.GetAttr(typeName)
82 attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
83 if err != nil {
84 return cty.UnknownVal(b.ImpliedType()), err
85 }
86 case blockS.MinItems != 1 && blockS.MaxItems != 1:
87 if blockS.Nesting == NestingGroup {
88 attrs[typeName] = blockS.EmptyValue()
89 } else {
90 attrs[typeName] = cty.NullVal(blockS.ImpliedType())
91 }
92 default:
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)
96 }
97
98 case NestingList:
99 switch {
100 case ty.HasAttribute(typeName):
101 coll := in.GetAttr(typeName)
102
103 switch {
104 case coll.IsNull():
105 attrs[typeName] = cty.NullVal(cty.List(blockS.ImpliedType()))
106 continue
107 case !coll.IsKnown():
108 attrs[typeName] = cty.UnknownVal(cty.List(blockS.ImpliedType()))
109 continue
110 }
111
112 if !coll.CanIterateElements() {
113 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
114 }
115 l := coll.LengthInt()
863486a6
AG
116
117 // Assume that if there are unknowns this could have come from
118 // a dynamic block, and we can't validate MinItems yet.
119 if l < blockS.MinItems && coll.IsWhollyKnown() {
107c1cdb
ND
120 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
121 }
122 if l > blockS.MaxItems && blockS.MaxItems > 0 {
123 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("too many items for attribute %q; cannot have more than %d", typeName, blockS.MaxItems)
124 }
125 if l == 0 {
126 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
127 continue
128 }
129 elems := make([]cty.Value, 0, l)
130 {
131 path = append(path, cty.GetAttrStep{Name: typeName})
132 for it := coll.ElementIterator(); it.Next(); {
133 var err error
134 idx, val := it.Element()
135 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
136 if err != nil {
137 return cty.UnknownVal(b.ImpliedType()), err
138 }
139 elems = append(elems, val)
140 }
141 }
142 attrs[typeName] = cty.ListVal(elems)
143 case blockS.MinItems == 0:
144 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
145 default:
146 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
147 }
148
149 case NestingSet:
150 switch {
151 case ty.HasAttribute(typeName):
152 coll := in.GetAttr(typeName)
153
154 switch {
155 case coll.IsNull():
156 attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType()))
157 continue
158 case !coll.IsKnown():
159 attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType()))
160 continue
161 }
162
163 if !coll.CanIterateElements() {
164 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
165 }
166 l := coll.LengthInt()
863486a6
AG
167
168 // Assume that if there are unknowns this could have come from
169 // a dynamic block, and we can't validate MinItems yet.
170 if l < blockS.MinItems && coll.IsWhollyKnown() {
107c1cdb
ND
171 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
172 }
173 if l > blockS.MaxItems && blockS.MaxItems > 0 {
174 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("too many items for attribute %q; cannot have more than %d", typeName, blockS.MaxItems)
175 }
176 if l == 0 {
177 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
178 continue
179 }
180 elems := make([]cty.Value, 0, l)
181 {
182 path = append(path, cty.GetAttrStep{Name: typeName})
183 for it := coll.ElementIterator(); it.Next(); {
184 var err error
185 idx, val := it.Element()
186 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
187 if err != nil {
188 return cty.UnknownVal(b.ImpliedType()), err
189 }
190 elems = append(elems, val)
191 }
192 }
193 attrs[typeName] = cty.SetVal(elems)
194 case blockS.MinItems == 0:
195 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
196 default:
197 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
198 }
199
200 case NestingMap:
201 switch {
202 case ty.HasAttribute(typeName):
203 coll := in.GetAttr(typeName)
204
205 switch {
206 case coll.IsNull():
207 attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType()))
208 continue
209 case !coll.IsKnown():
210 attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType()))
211 continue
212 }
213
214 if !coll.CanIterateElements() {
215 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
216 }
217 l := coll.LengthInt()
218 if l == 0 {
219 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
220 continue
221 }
222 elems := make(map[string]cty.Value)
223 {
224 path = append(path, cty.GetAttrStep{Name: typeName})
225 for it := coll.ElementIterator(); it.Next(); {
226 var err error
227 key, val := it.Element()
228 if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
229 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
230 }
231 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
232 if err != nil {
233 return cty.UnknownVal(b.ImpliedType()), err
234 }
235 elems[key.AsString()] = val
236 }
237 }
238
239 // If the attribute values here contain any DynamicPseudoTypes,
240 // the concrete type must be an object.
241 useObject := false
242 switch {
243 case coll.Type().IsObjectType():
244 useObject = true
245 default:
246 // It's possible that we were given a map, and need to coerce it to an object
247 ety := coll.Type().ElementType()
248 for _, v := range elems {
249 if !v.Type().Equals(ety) {
250 useObject = true
251 break
252 }
253 }
254 }
255
256 if useObject {
257 attrs[typeName] = cty.ObjectVal(elems)
258 } else {
259 attrs[typeName] = cty.MapVal(elems)
260 }
261 default:
262 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
263 }
264
265 default:
266 // should never happen because above is exhaustive
267 panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting))
268 }
269 }
270
271 return cty.ObjectVal(attrs), nil
272}
273
274func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
275 val, err := convert.Convert(in, a.Type)
276 if err != nil {
277 return cty.UnknownVal(a.Type), path.NewError(err)
278 }
279 return val, nil
280}