|
1 | 1 | package typeexpr |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "sort" |
| 5 | + "strconv" |
| 6 | + |
4 | 7 | "github.com/zclconf/go-cty/cty" |
| 8 | + "github.com/zclconf/go-cty/cty/convert" |
5 | 9 | ) |
6 | 10 |
|
7 | 11 | // Defaults represents a type tree which may contain default values for |
8 | 12 | // optional object attributes at any level. This is used to apply nested |
9 | | -// defaults to an input value before converting it to the concrete type. |
| 13 | +// defaults to a given cty.Value before converting it to a concrete type. |
10 | 14 | type Defaults struct { |
11 | 15 | // Type of the node for which these defaults apply. This is necessary in |
12 | 16 | // order to determine how to inspect the Defaults and Children collections. |
@@ -35,123 +39,168 @@ type Defaults struct { |
35 | 39 | // caller will have better context to report useful type conversion failure |
36 | 40 | // diagnostics. |
37 | 41 | func (d *Defaults) Apply(val cty.Value) cty.Value { |
38 | | - val, err := cty.TransformWithTransformer(val, &defaultsTransformer{defaults: d}) |
39 | | - |
40 | | - // The transformer should never return an error. |
41 | | - if err != nil { |
42 | | - panic(err) |
43 | | - } |
44 | | - |
45 | | - return val |
| 42 | + return d.apply(val) |
46 | 43 | } |
47 | 44 |
|
48 | | -// defaultsTransformer implements cty.Transformer, as a pre-order traversal, |
49 | | -// applying defaults as it goes. The pre-order traversal allows us to specify |
50 | | -// defaults more loosely for structural types, as the defaults for the types |
51 | | -// will be applied to the default value later in the walk. |
52 | | -type defaultsTransformer struct { |
53 | | - defaults *Defaults |
54 | | -} |
55 | | - |
56 | | -var _ cty.Transformer = (*defaultsTransformer)(nil) |
57 | | - |
58 | | -func (t *defaultsTransformer) Enter(p cty.Path, v cty.Value) (cty.Value, error) { |
59 | | - // Cannot apply defaults to an unknown value |
60 | | - if !v.IsKnown() { |
61 | | - return v, nil |
| 45 | +func (d *Defaults) apply(v cty.Value) cty.Value { |
| 46 | + // We don't apply defaults to null values or unknown values. To be clear, |
| 47 | + // we will overwrite children values with defaults if they are null but not |
| 48 | + // if the actual value is null. |
| 49 | + if !v.IsKnown() || v.IsNull() { |
| 50 | + return v |
62 | 51 | } |
63 | 52 |
|
64 | | - // Look up the defaults for this path. |
65 | | - defaults := t.defaults.traverse(p) |
66 | | - |
67 | | - // If we have no defaults, nothing to do. |
68 | | - if len(defaults) == 0 { |
69 | | - return v, nil |
| 53 | + // Also, do nothing if we have no defaults to apply. |
| 54 | + if len(d.DefaultValues) == 0 && len(d.Children) == 0 { |
| 55 | + return v |
70 | 56 | } |
71 | 57 |
|
72 | | - // Ensure we are working with an object or map. |
73 | | - vt := v.Type() |
74 | | - if !vt.IsObjectType() && !vt.IsMapType() { |
75 | | - // Cannot apply defaults because the value type is incompatible. |
76 | | - // We'll ignore this and let the later conversion stage display a |
77 | | - // more useful diagnostic. |
78 | | - return v, nil |
| 58 | + v, marks := v.Unmark() |
| 59 | + |
| 60 | + switch { |
| 61 | + case v.Type().IsSetType(), v.Type().IsListType(), v.Type().IsTupleType(): |
| 62 | + values := d.applyAsSlice(v) |
| 63 | + |
| 64 | + if v.Type().IsSetType() { |
| 65 | + if len(values) == 0 { |
| 66 | + v = cty.SetValEmpty(v.Type().ElementType()) |
| 67 | + break |
| 68 | + } |
| 69 | + if converts := d.unifyAsSlice(values); len(converts) > 0 { |
| 70 | + v = cty.SetVal(converts).WithMarks(marks) |
| 71 | + break |
| 72 | + } |
| 73 | + } else if v.Type().IsListType() { |
| 74 | + if len(values) == 0 { |
| 75 | + v = cty.ListValEmpty(v.Type().ElementType()) |
| 76 | + break |
| 77 | + } |
| 78 | + if converts := d.unifyAsSlice(values); len(converts) > 0 { |
| 79 | + v = cty.ListVal(converts) |
| 80 | + break |
| 81 | + } |
| 82 | + } |
| 83 | + v = cty.TupleVal(values) |
| 84 | + case v.Type().IsObjectType(), v.Type().IsMapType(): |
| 85 | + values := d.applyAsMap(v) |
| 86 | + |
| 87 | + for key, defaultValue := range d.DefaultValues { |
| 88 | + if value, ok := values[key]; !ok || value.IsNull() { |
| 89 | + if defaults, ok := d.Children[key]; ok { |
| 90 | + values[key] = defaults.apply(defaultValue) |
| 91 | + continue |
| 92 | + } |
| 93 | + values[key] = defaultValue |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + if v.Type().IsMapType() { |
| 98 | + if len(values) == 0 { |
| 99 | + v = cty.MapValEmpty(v.Type().ElementType()) |
| 100 | + break |
| 101 | + } |
| 102 | + if converts := d.unifyAsMap(values); len(converts) > 0 { |
| 103 | + v = cty.MapVal(converts) |
| 104 | + break |
| 105 | + } |
| 106 | + } |
| 107 | + v = cty.ObjectVal(values) |
79 | 108 | } |
80 | 109 |
|
81 | | - // Unmark the value and reapply the marks later. |
82 | | - v, valMarks := v.Unmark() |
| 110 | + return v.WithMarks(marks) |
| 111 | +} |
83 | 112 |
|
84 | | - // Convert the given value into an attribute map (if it's non-null and |
85 | | - // non-empty). |
86 | | - attrs := make(map[string]cty.Value) |
87 | | - if !v.IsNull() && v.LengthInt() > 0 { |
88 | | - attrs = v.AsValueMap() |
| 113 | +func (d *Defaults) applyAsSlice(value cty.Value) []cty.Value { |
| 114 | + var elements []cty.Value |
| 115 | + for ix, element := range value.AsValueSlice() { |
| 116 | + if childDefaults := d.getChild(ix); childDefaults != nil { |
| 117 | + element = childDefaults.apply(element) |
| 118 | + elements = append(elements, element) |
| 119 | + continue |
| 120 | + } |
| 121 | + elements = append(elements, element) |
89 | 122 | } |
| 123 | + return elements |
| 124 | +} |
90 | 125 |
|
91 | | - // Apply defaults where attributes are missing, constructing a new |
92 | | - // value with the same marks. |
93 | | - for attr, defaultValue := range defaults { |
94 | | - if attrValue, ok := attrs[attr]; !ok || attrValue.IsNull() { |
95 | | - attrs[attr] = defaultValue |
| 126 | +func (d *Defaults) applyAsMap(value cty.Value) map[string]cty.Value { |
| 127 | + elements := make(map[string]cty.Value) |
| 128 | + for key, element := range value.AsValueMap() { |
| 129 | + if childDefaults := d.getChild(key); childDefaults != nil { |
| 130 | + elements[key] = childDefaults.apply(element) |
| 131 | + continue |
96 | 132 | } |
| 133 | + elements[key] = element |
97 | 134 | } |
98 | | - |
99 | | - // We construct an object even if the input value was a map, as the |
100 | | - // type of an attribute's default value may be incompatible with the |
101 | | - // map element type. |
102 | | - return cty.ObjectVal(attrs).WithMarks(valMarks), nil |
| 135 | + return elements |
103 | 136 | } |
104 | 137 |
|
105 | | -func (t *defaultsTransformer) Exit(p cty.Path, v cty.Value) (cty.Value, error) { |
106 | | - return v, nil |
| 138 | +func (d *Defaults) getChild(key interface{}) *Defaults { |
| 139 | + switch { |
| 140 | + case d.Type.IsMapType(), d.Type.IsSetType(), d.Type.IsListType(): |
| 141 | + return d.Children[""] |
| 142 | + case d.Type.IsTupleType(): |
| 143 | + return d.Children[strconv.Itoa(key.(int))] |
| 144 | + case d.Type.IsObjectType(): |
| 145 | + return d.Children[key.(string)] |
| 146 | + default: |
| 147 | + return nil |
| 148 | + } |
107 | 149 | } |
108 | 150 |
|
109 | | -// traverse walks the abstract defaults structure for a given path, returning |
110 | | -// a set of default values (if any are present) or nil (if not). This operation |
111 | | -// differs from applying a path to a value because we need to customize the |
112 | | -// traversal steps for collection types, where a single set of defaults can be |
113 | | -// applied to an arbitrary number of elements. |
114 | | -func (d *Defaults) traverse(path cty.Path) map[string]cty.Value { |
115 | | - if len(path) == 0 { |
116 | | - return d.DefaultValues |
| 151 | +func (d *Defaults) unifyAsSlice(values []cty.Value) []cty.Value { |
| 152 | + var types []cty.Type |
| 153 | + for _, value := range values { |
| 154 | + types = append(types, value.Type()) |
| 155 | + } |
| 156 | + unify, conversions := convert.UnifyUnsafe(types) |
| 157 | + if unify == cty.NilType { |
| 158 | + return nil |
117 | 159 | } |
118 | 160 |
|
119 | | - switch s := path[0].(type) { |
120 | | - case cty.GetAttrStep: |
121 | | - if d.Type.IsObjectType() { |
122 | | - // Attribute path steps are normally applied to objects, where each |
123 | | - // attribute may have different defaults. |
124 | | - return d.traverseChild(s.Name, path) |
125 | | - } else if d.Type.IsMapType() { |
126 | | - // Literal values for maps can result in attribute path steps, in which |
127 | | - // case we need to disregard the attribute name, as maps can have only |
128 | | - // one child. |
129 | | - return d.traverseChild("", path) |
| 161 | + var converts []cty.Value |
| 162 | + for ix := 0; ix < len(conversions); ix++ { |
| 163 | + if conversions[ix] == nil { |
| 164 | + converts = append(converts, values[ix]) |
| 165 | + continue |
130 | 166 | } |
131 | 167 |
|
132 | | - return nil |
133 | | - case cty.IndexStep: |
134 | | - if d.Type.IsTupleType() { |
135 | | - // Tuples can have different types for each element, so we look |
136 | | - // up the defaults based on the index key. |
137 | | - return d.traverseChild(s.Key.AsBigFloat().String(), path) |
138 | | - } else if d.Type.IsCollectionType() { |
139 | | - // Defaults for collection element types are stored with a blank |
140 | | - // key, so we disregard the index key. |
141 | | - return d.traverseChild("", path) |
| 168 | + converted, err := conversions[ix](values[ix]) |
| 169 | + if err != nil { |
| 170 | + return nil |
142 | 171 | } |
143 | | - return nil |
144 | | - default: |
145 | | - // At time of writing there are no other path step types. |
146 | | - return nil |
| 172 | + converts = append(converts, converted) |
147 | 173 | } |
| 174 | + return converts |
148 | 175 | } |
149 | 176 |
|
150 | | -// traverseChild continues the traversal for a given child key, and mutually |
151 | | -// recurses with traverse. |
152 | | -func (d *Defaults) traverseChild(name string, path cty.Path) map[string]cty.Value { |
153 | | - if child, ok := d.Children[name]; ok { |
154 | | - return child.traverse(path[1:]) |
| 177 | +func (d *Defaults) unifyAsMap(values map[string]cty.Value) map[string]cty.Value { |
| 178 | + var keys []string |
| 179 | + for key := range values { |
| 180 | + keys = append(keys, key) |
| 181 | + } |
| 182 | + sort.Strings(keys) |
| 183 | + |
| 184 | + var types []cty.Type |
| 185 | + for _, key := range keys { |
| 186 | + types = append(types, values[key].Type()) |
| 187 | + } |
| 188 | + unify, conversions := convert.UnifyUnsafe(types) |
| 189 | + if unify == cty.NilType { |
| 190 | + return nil |
| 191 | + } |
| 192 | + |
| 193 | + converts := make(map[string]cty.Value) |
| 194 | + for ix, key := range keys { |
| 195 | + if conversions[ix] == nil { |
| 196 | + converts[key] = values[key] |
| 197 | + continue |
| 198 | + } |
| 199 | + |
| 200 | + var err error |
| 201 | + if converts[key], err = conversions[ix](values[key]); err != nil { |
| 202 | + return nil |
| 203 | + } |
155 | 204 | } |
156 | | - return nil |
| 205 | + return converts |
157 | 206 | } |
0 commit comments