]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / configs / configschema / coerce_value.go
1 package configschema
2
3 import (
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.
26 func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
27 var path cty.Path
28 return b.coerceValue(in, path)
29 }
30
31 func (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()
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)
118 }
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)
121 }
122 if l == 0 {
123 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
124 continue
125 }
126 elems := make([]cty.Value, 0, l)
127 {
128 path = append(path, cty.GetAttrStep{Name: typeName})
129 for it := coll.ElementIterator(); it.Next(); {
130 var err error
131 idx, val := it.Element()
132 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
133 if err != nil {
134 return cty.UnknownVal(b.ImpliedType()), err
135 }
136 elems = append(elems, val)
137 }
138 }
139 attrs[typeName] = cty.ListVal(elems)
140 case blockS.MinItems == 0:
141 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
142 default:
143 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
144 }
145
146 case NestingSet:
147 switch {
148 case ty.HasAttribute(typeName):
149 coll := in.GetAttr(typeName)
150
151 switch {
152 case coll.IsNull():
153 attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType()))
154 continue
155 case !coll.IsKnown():
156 attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType()))
157 continue
158 }
159
160 if !coll.CanIterateElements() {
161 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
162 }
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)
166 }
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)
169 }
170 if l == 0 {
171 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
172 continue
173 }
174 elems := make([]cty.Value, 0, l)
175 {
176 path = append(path, cty.GetAttrStep{Name: typeName})
177 for it := coll.ElementIterator(); it.Next(); {
178 var err error
179 idx, val := it.Element()
180 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
181 if err != nil {
182 return cty.UnknownVal(b.ImpliedType()), err
183 }
184 elems = append(elems, val)
185 }
186 }
187 attrs[typeName] = cty.SetVal(elems)
188 case blockS.MinItems == 0:
189 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
190 default:
191 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
192 }
193
194 case NestingMap:
195 switch {
196 case ty.HasAttribute(typeName):
197 coll := in.GetAttr(typeName)
198
199 switch {
200 case coll.IsNull():
201 attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType()))
202 continue
203 case !coll.IsKnown():
204 attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType()))
205 continue
206 }
207
208 if !coll.CanIterateElements() {
209 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
210 }
211 l := coll.LengthInt()
212 if l == 0 {
213 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
214 continue
215 }
216 elems := make(map[string]cty.Value)
217 {
218 path = append(path, cty.GetAttrStep{Name: typeName})
219 for it := coll.ElementIterator(); it.Next(); {
220 var err error
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")
224 }
225 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
226 if err != nil {
227 return cty.UnknownVal(b.ImpliedType()), err
228 }
229 elems[key.AsString()] = val
230 }
231 }
232
233 // If the attribute values here contain any DynamicPseudoTypes,
234 // the concrete type must be an object.
235 useObject := false
236 switch {
237 case coll.Type().IsObjectType():
238 useObject = true
239 default:
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) {
244 useObject = true
245 break
246 }
247 }
248 }
249
250 if useObject {
251 attrs[typeName] = cty.ObjectVal(elems)
252 } else {
253 attrs[typeName] = cty.MapVal(elems)
254 }
255 default:
256 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
257 }
258
259 default:
260 // should never happen because above is exhaustive
261 panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting))
262 }
263 }
264
265 return cty.ObjectVal(attrs), nil
266 }
267
268 func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
269 val, err := convert.Convert(in, a.Type)
270 if err != nil {
271 return cty.UnknownVal(a.Type), path.NewError(err)
272 }
273 return val, nil
274 }