-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Expand file tree
/
Copy pathdecoder_spec.go
More file actions
191 lines (170 loc) · 5.55 KB
/
decoder_spec.go
File metadata and controls
191 lines (170 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package configschema
import (
"runtime"
"sync"
"unsafe"
"github.com/hashicorp/hcl/v2/hcldec"
)
var mapLabelNames = []string{"key"}
// specCache is a global cache of all the generated hcldec.Spec values for
// Blocks. This cache is used by the Block.DecoderSpec method to memoize calls
// and prevent unnecessary regeneration of the spec, especially when they are
// large and deeply nested.
// Caching these externally rather than within the struct is required because
// Blocks are used by value and copied when working with NestedBlocks, and the
// copying of the value prevents any safe synchronisation of the struct itself.
//
// While we are using the *Block pointer as the cache key, and the Block
// contents are mutable, once a Block is created it is treated as immutable for
// the duration of its life. Because a Block is a representation of a logical
// schema, which cannot change while it's being used, any modifications to the
// schema during execution would be an error.
type specCache struct {
sync.Mutex
specs map[uintptr]hcldec.Spec
}
var decoderSpecCache = specCache{
specs: map[uintptr]hcldec.Spec{},
}
// get returns the Spec associated with eth given Block, or nil if non is
// found.
func (s *specCache) get(b *Block) hcldec.Spec {
s.Lock()
defer s.Unlock()
k := uintptr(unsafe.Pointer(b))
return s.specs[k]
}
// set stores the given Spec as being the result of b.DecoderSpec().
func (s *specCache) set(b *Block, spec hcldec.Spec) {
s.Lock()
defer s.Unlock()
// the uintptr value gets us a unique identifier for each block, without
// tying this to the block value itself.
k := uintptr(unsafe.Pointer(b))
if _, ok := s.specs[k]; ok {
return
}
s.specs[k] = spec
// This must use a finalizer tied to the Block, otherwise we'll continue to
// build up Spec values as the Blocks are recycled.
runtime.SetFinalizer(b, s.delete)
}
// delete removes the spec associated with the given Block.
func (s *specCache) delete(b *Block) {
s.Lock()
defer s.Unlock()
k := uintptr(unsafe.Pointer(b))
delete(s.specs, k)
}
// DecoderSpec returns a hcldec.Spec that can be used to decode a HCL Body
// using the facilities in the hcldec package.
//
// The returned specification is guaranteed to return a value of the same type
// returned by method ImpliedType, but it may contain null values if any of the
// block attributes are defined as optional and/or computed respectively.
func (b *Block) DecoderSpec() hcldec.Spec {
ret := hcldec.ObjectSpec{}
if b == nil {
return ret
}
if spec := decoderSpecCache.get(b); spec != nil {
return spec
}
for name, attrS := range b.Attributes {
ret[name] = attrS.decoderSpec(name)
}
for name, blockS := range b.BlockTypes {
if _, exists := ret[name]; exists {
// This indicates an invalid schema, since it's not valid to
// define both an attribute and a block type of the same name.
// However, we don't raise this here since it's checked by
// InternalValidate.
continue
}
childSpec := blockS.Block.DecoderSpec()
// We can only validate 0 or 1 for MinItems, because a dynamic block
// may satisfy any number of min items while only having a single
// block in the config. We cannot validate MaxItems because a
// configuration may have any number of dynamic blocks
minItems := 0
if blockS.MinItems > 1 {
minItems = 1
}
switch blockS.Nesting {
case NestingSingle, NestingGroup:
ret[name] = &hcldec.BlockSpec{
TypeName: name,
Nested: childSpec,
Required: blockS.MinItems == 1,
}
if blockS.Nesting == NestingGroup {
ret[name] = &hcldec.DefaultSpec{
Primary: ret[name],
Default: &hcldec.LiteralSpec{
Value: blockS.EmptyValue(),
},
}
}
case NestingList:
// We prefer to use a list where possible, since it makes our
// implied type more complete, but if there are any
// dynamically-typed attributes inside we must use a tuple
// instead, at the expense of our type then not being predictable.
if blockS.Block.ImpliedType().HasDynamicTypes() {
ret[name] = &hcldec.BlockTupleSpec{
TypeName: name,
Nested: childSpec,
MinItems: minItems,
}
} else {
ret[name] = &hcldec.BlockListSpec{
TypeName: name,
Nested: childSpec,
MinItems: minItems,
}
}
case NestingSet:
// We forbid dynamically-typed attributes inside NestingSet in
// InternalValidate, so we don't do anything special to handle
// that here. (There is no set analog to tuple and object types,
// because cty's set implementation depends on knowing the static
// type in order to properly compute its internal hashes.)
ret[name] = &hcldec.BlockSetSpec{
TypeName: name,
Nested: childSpec,
MinItems: minItems,
}
case NestingMap:
// We prefer to use a list where possible, since it makes our
// implied type more complete, but if there are any
// dynamically-typed attributes inside we must use a tuple
// instead, at the expense of our type then not being predictable.
if blockS.Block.ImpliedType().HasDynamicTypes() {
ret[name] = &hcldec.BlockObjectSpec{
TypeName: name,
Nested: childSpec,
LabelNames: mapLabelNames,
}
} else {
ret[name] = &hcldec.BlockMapSpec{
TypeName: name,
Nested: childSpec,
LabelNames: mapLabelNames,
}
}
default:
// Invalid nesting type is just ignored. It's checked by
// InternalValidate.
continue
}
}
decoderSpecCache.set(b, ret)
return ret
}
func (a *Attribute) decoderSpec(name string) hcldec.Spec {
return &hcldec.AttrSpec{
Name: name,
Type: a.Type,
Required: a.Required,
}
}