6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/hashicorp/hcl2/hcl/hclsyntax"
8 "github.com/zclconf/go-cty/cty"
9 "github.com/zclconf/go-cty/cty/convert"
12 // body is the implementation of "Body" used for files processed with the JSON
17 // If non-nil, the keys of this map cause the corresponding attributes to
18 // be treated as non-existing. This is used when Body.PartialContent is
19 // called, to produce the "remaining content" Body.
20 hiddenAttrs map[string]struct{}
23 // expression is the implementation of "Expression" used for files processed
24 // with the JSON parser.
25 type expression struct {
29 func (b *body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
30 content, newBody, diags := b.PartialContent(schema)
32 hiddenAttrs := newBody.(*body).hiddenAttrs
34 var nameSuggestions []string
35 for _, attrS := range schema.Attributes {
36 if _, ok := hiddenAttrs[attrS.Name]; !ok {
37 // Only suggest an attribute name if we didn't use it already.
38 nameSuggestions = append(nameSuggestions, attrS.Name)
41 for _, blockS := range schema.Blocks {
42 // Blocks can appear multiple times, so we'll suggest their type
43 // names regardless of whether they've already been used.
44 nameSuggestions = append(nameSuggestions, blockS.Type)
47 jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
48 diags = append(diags, attrDiags...)
50 for _, attr := range jsonAttrs {
53 // Ignore "//" keys in objects representing bodies, to allow
54 // their use as comments.
58 if _, ok := hiddenAttrs[k]; !ok {
59 suggestion := nameSuggestion(k, nameSuggestions)
61 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
64 diags = append(diags, &hcl.Diagnostic{
65 Severity: hcl.DiagError,
66 Summary: "Extraneous JSON object property",
67 Detail: fmt.Sprintf("No attribute or block type is named %q.%s", k, suggestion),
68 Subject: &attr.NameRange,
69 Context: attr.Range().Ptr(),
77 func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
78 var diags hcl.Diagnostics
80 jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
81 diags = append(diags, attrDiags...)
83 usedNames := map[string]struct{}{}
84 if b.hiddenAttrs != nil {
85 for k := range b.hiddenAttrs {
86 usedNames[k] = struct{}{}
90 content := &hcl.BodyContent{
91 Attributes: map[string]*hcl.Attribute{},
94 MissingItemRange: b.MissingItemRange(),
97 // Create some more convenient data structures for our work below.
98 attrSchemas := map[string]hcl.AttributeSchema{}
99 blockSchemas := map[string]hcl.BlockHeaderSchema{}
100 for _, attrS := range schema.Attributes {
101 attrSchemas[attrS.Name] = attrS
103 for _, blockS := range schema.Blocks {
104 blockSchemas[blockS.Type] = blockS
107 for _, jsonAttr := range jsonAttrs {
108 attrName := jsonAttr.Name
109 if _, used := b.hiddenAttrs[attrName]; used {
113 if attrS, defined := attrSchemas[attrName]; defined {
114 if existing, exists := content.Attributes[attrName]; exists {
115 diags = append(diags, &hcl.Diagnostic{
116 Severity: hcl.DiagError,
117 Summary: "Duplicate attribute definition",
118 Detail: fmt.Sprintf("The attribute %q was already defined at %s.", attrName, existing.Range),
119 Subject: &jsonAttr.NameRange,
120 Context: jsonAttr.Range().Ptr(),
125 content.Attributes[attrS.Name] = &hcl.Attribute{
127 Expr: &expression{src: jsonAttr.Value},
128 Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
129 NameRange: jsonAttr.NameRange,
131 usedNames[attrName] = struct{}{}
133 } else if blockS, defined := blockSchemas[attrName]; defined {
135 blockDiags := b.unpackBlock(bv, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks)
136 diags = append(diags, blockDiags...)
137 usedNames[attrName] = struct{}{}
140 // We ignore anything that isn't defined because that's the
141 // PartialContent contract. The Content method will catch leftovers.
144 // Make sure we got all the required attributes.
145 for _, attrS := range schema.Attributes {
149 if _, defined := content.Attributes[attrS.Name]; !defined {
150 diags = append(diags, &hcl.Diagnostic{
151 Severity: hcl.DiagError,
152 Summary: "Missing required attribute",
153 Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
154 Subject: b.MissingItemRange().Ptr(),
161 hiddenAttrs: usedNames,
164 return content, unusedBody, diags
167 // JustAttributes for JSON bodies interprets all properties of the wrapped
168 // JSON object as attributes and returns them.
169 func (b *body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
170 var diags hcl.Diagnostics
171 attrs := make(map[string]*hcl.Attribute)
173 obj, ok := b.val.(*objectVal)
175 diags = append(diags, &hcl.Diagnostic{
176 Severity: hcl.DiagError,
177 Summary: "Incorrect JSON value type",
178 Detail: "A JSON object is required here, defining the attributes for this block.",
179 Subject: b.val.StartRange().Ptr(),
184 for _, jsonAttr := range obj.Attrs {
185 name := jsonAttr.Name
187 // Ignore "//" keys in objects representing bodies, to allow
188 // their use as comments.
192 if _, hidden := b.hiddenAttrs[name]; hidden {
196 if existing, exists := attrs[name]; exists {
197 diags = append(diags, &hcl.Diagnostic{
198 Severity: hcl.DiagError,
199 Summary: "Duplicate attribute definition",
200 Detail: fmt.Sprintf("The attribute %q was already defined at %s.", name, existing.Range),
201 Subject: &jsonAttr.NameRange,
206 attrs[name] = &hcl.Attribute{
208 Expr: &expression{src: jsonAttr.Value},
209 Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
210 NameRange: jsonAttr.NameRange,
214 // No diagnostics possible here, since the parser already took care of
215 // finding duplicates and every JSON value can be a valid attribute value.
219 func (b *body) MissingItemRange() hcl.Range {
220 switch tv := b.val.(type) {
226 // Should not happen in correct operation, but might show up if the
227 // input is invalid and we are producing partial results.
228 return tv.StartRange()
232 func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labelsLeft []string, labelsUsed []string, labelRanges []hcl.Range, blocks *hcl.Blocks) (diags hcl.Diagnostics) {
233 if len(labelsLeft) > 0 {
234 labelName := labelsLeft[0]
235 jsonAttrs, attrDiags := b.collectDeepAttrs(v, &labelName)
236 diags = append(diags, attrDiags...)
238 if len(jsonAttrs) == 0 {
239 diags = diags.Append(&hcl.Diagnostic{
240 Severity: hcl.DiagError,
241 Summary: "Missing block label",
242 Detail: fmt.Sprintf("At least one object property is required, whose name represents the %s block's %s.", typeName, labelName),
243 Subject: v.StartRange().Ptr(),
247 labelsUsed := append(labelsUsed, "")
248 labelRanges := append(labelRanges, hcl.Range{})
249 for _, p := range jsonAttrs {
251 labelsUsed[len(labelsUsed)-1] = pk
252 labelRanges[len(labelRanges)-1] = p.NameRange
253 diags = append(diags, b.unpackBlock(p.Value, typeName, typeRange, labelsLeft[1:], labelsUsed, labelRanges, blocks)...)
258 // By the time we get here, we've peeled off all the labels and we're ready
259 // to deal with the block's actual content.
261 // need to copy the label slices because their underlying arrays will
262 // continue to be mutated after we return.
263 labels := make([]string, len(labelsUsed))
264 copy(labels, labelsUsed)
265 labelR := make([]hcl.Range, len(labelRanges))
266 copy(labelR, labelRanges)
268 switch tv := v.(type) {
270 // Single instance of the block
271 *blocks = append(*blocks, &hcl.Block{
278 DefRange: tv.OpenRange,
279 TypeRange: *typeRange,
283 // Multiple instances of the block
284 for _, av := range tv.Values {
285 *blocks = append(*blocks, &hcl.Block{
289 val: av, // might be mistyped; we'll find out when content is requested for this body
292 DefRange: tv.OpenRange,
293 TypeRange: *typeRange,
298 diags = diags.Append(&hcl.Diagnostic{
299 Severity: hcl.DiagError,
300 Summary: "Incorrect JSON value type",
301 Detail: fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName),
302 Subject: v.StartRange().Ptr(),
308 // collectDeepAttrs takes either a single object or an array of objects and
309 // flattens it into a list of object attributes, collecting attributes from
310 // all of the objects in a given array.
312 // Ordering is preserved, so a list of objects that each have one property
313 // will result in those properties being returned in the same order as the
314 // objects appeared in the array.
316 // This is appropriate for use only for objects representing bodies or labels
319 // The labelName argument, if non-null, is used to tailor returned error
320 // messages to refer to block labels rather than attributes and child blocks.
321 // It has no other effect.
322 func (b *body) collectDeepAttrs(v node, labelName *string) ([]*objectAttr, hcl.Diagnostics) {
323 var diags hcl.Diagnostics
324 var attrs []*objectAttr
326 switch tv := v.(type) {
329 attrs = append(attrs, tv.Attrs...)
332 for _, ev := range tv.Values {
333 switch tev := ev.(type) {
335 attrs = append(attrs, tev.Attrs...)
337 if labelName != nil {
338 diags = append(diags, &hcl.Diagnostic{
339 Severity: hcl.DiagError,
340 Summary: "Incorrect JSON value type",
341 Detail: fmt.Sprintf("A JSON object is required here, to specify %s labels for this block.", *labelName),
342 Subject: ev.StartRange().Ptr(),
345 diags = append(diags, &hcl.Diagnostic{
346 Severity: hcl.DiagError,
347 Summary: "Incorrect JSON value type",
348 Detail: "A JSON object is required here, to define attributes and child blocks.",
349 Subject: ev.StartRange().Ptr(),
356 if labelName != nil {
357 diags = append(diags, &hcl.Diagnostic{
358 Severity: hcl.DiagError,
359 Summary: "Incorrect JSON value type",
360 Detail: fmt.Sprintf("Either a JSON object or JSON array of objects is required here, to specify %s labels for this block.", *labelName),
361 Subject: v.StartRange().Ptr(),
364 diags = append(diags, &hcl.Diagnostic{
365 Severity: hcl.DiagError,
366 Summary: "Incorrect JSON value type",
367 Detail: "Either a JSON object or JSON array of objects is required here, to define attributes and child blocks.",
368 Subject: v.StartRange().Ptr(),
376 func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
377 switch v := e.src.(type) {
380 // Parse string contents as a HCL native language expression.
381 // We only do this if we have a context, so passing a nil context
382 // is how the caller specifies that interpolations are not allowed
383 // and that the string should just be returned verbatim.
384 templateSrc := v.Value
385 expr, diags := hclsyntax.ParseTemplate(
389 // This won't produce _exactly_ the right result, since
390 // the hclsyntax parser can't "see" any escapes we removed
391 // while parsing JSON, but it's better than nothing.
393 Line: v.SrcRange.Start.Line,
395 // skip over the opening quote mark
396 Byte: v.SrcRange.Start.Byte + 1,
397 Column: v.SrcRange.Start.Column + 1,
400 if diags.HasErrors() {
401 return cty.DynamicVal, diags
403 val, evalDiags := expr.Value(ctx)
404 diags = append(diags, evalDiags...)
408 return cty.StringVal(v.Value), nil
410 return cty.NumberVal(v.Value), nil
412 return cty.BoolVal(v.Value), nil
414 vals := []cty.Value{}
415 for _, jsonVal := range v.Values {
416 val, _ := (&expression{src: jsonVal}).Value(ctx)
417 vals = append(vals, val)
419 return cty.TupleVal(vals), nil
421 var diags hcl.Diagnostics
422 attrs := map[string]cty.Value{}
423 attrRanges := map[string]hcl.Range{}
425 for _, jsonAttr := range v.Attrs {
426 // In this one context we allow keys to contain interpolation
427 // experessions too, assuming we're evaluating in interpolation
428 // mode. This achieves parity with the native syntax where
429 // object expressions can have dynamic keys, while block contents
431 name, nameDiags := (&expression{src: &stringVal{
432 Value: jsonAttr.Name,
433 SrcRange: jsonAttr.NameRange,
435 val, valDiags := (&expression{src: jsonAttr.Value}).Value(ctx)
436 diags = append(diags, nameDiags...)
437 diags = append(diags, valDiags...)
440 name, err = convert.Convert(name, cty.String)
442 diags = append(diags, &hcl.Diagnostic{
443 Severity: hcl.DiagError,
444 Summary: "Invalid object key expression",
445 Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err),
446 Subject: &jsonAttr.NameRange,
451 diags = append(diags, &hcl.Diagnostic{
452 Severity: hcl.DiagError,
453 Summary: "Invalid object key expression",
454 Detail: "Cannot use null value as an object key.",
455 Subject: &jsonAttr.NameRange,
460 // This is a bit of a weird case, since our usual rules require
461 // us to tolerate unknowns and just represent the result as
462 // best we can but if we don't know the key then we can't
463 // know the type of our object at all, and thus we must turn
464 // the whole thing into cty.DynamicVal. This is consistent with
465 // how this situation is handled in the native syntax.
466 // We'll keep iterating so we can collect other errors in
467 // subsequent attributes.
471 nameStr := name.AsString()
472 if _, defined := attrs[nameStr]; defined {
473 diags = append(diags, &hcl.Diagnostic{
474 Severity: hcl.DiagError,
475 Summary: "Duplicate object attribute",
476 Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]),
477 Subject: &jsonAttr.NameRange,
482 attrRanges[nameStr] = jsonAttr.NameRange
485 // We encountered an unknown key somewhere along the way, so
486 // we can't know what our type will eventually be.
487 return cty.DynamicVal, diags
489 return cty.ObjectVal(attrs), diags
491 // Default to DynamicVal so that ASTs containing invalid nodes can
492 // still be partially-evaluated.
493 return cty.DynamicVal, nil
497 func (e *expression) Variables() []hcl.Traversal {
498 var vars []hcl.Traversal
500 switch v := e.src.(type) {
502 templateSrc := v.Value
503 expr, diags := hclsyntax.ParseTemplate(
507 // This won't produce _exactly_ the right result, since
508 // the hclsyntax parser can't "see" any escapes we removed
509 // while parsing JSON, but it's better than nothing.
511 Line: v.SrcRange.Start.Line,
513 // skip over the opening quote mark
514 Byte: v.SrcRange.Start.Byte + 1,
515 Column: v.SrcRange.Start.Column + 1,
518 if diags.HasErrors() {
521 return expr.Variables()
524 for _, jsonVal := range v.Values {
525 vars = append(vars, (&expression{src: jsonVal}).Variables()...)
528 for _, jsonAttr := range v.Attrs {
529 vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
536 func (e *expression) Range() hcl.Range {
540 func (e *expression) StartRange() hcl.Range {
541 return e.src.StartRange()
544 // Implementation for hcl.AbsTraversalForExpr.
545 func (e *expression) AsTraversal() hcl.Traversal {
546 // In JSON-based syntax a traversal is given as a string containing
547 // traversal syntax as defined by hclsyntax.ParseTraversalAbs.
549 switch v := e.src.(type) {
551 traversal, diags := hclsyntax.ParseTraversalAbs([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
552 if diags.HasErrors() {
561 // Implementation for hcl.ExprCall.
562 func (e *expression) ExprCall() *hcl.StaticCall {
563 // In JSON-based syntax a static call is given as a string containing
564 // an expression in the native syntax that also supports ExprCall.
566 switch v := e.src.(type) {
568 expr, diags := hclsyntax.ParseExpression([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
569 if diags.HasErrors() {
573 call, diags := hcl.ExprCall(expr)
574 if diags.HasErrors() {
584 // Implementation for hcl.ExprList.
585 func (e *expression) ExprList() []hcl.Expression {
586 switch v := e.src.(type) {
588 ret := make([]hcl.Expression, len(v.Values))
589 for i, node := range v.Values {
590 ret[i] = &expression{src: node}
598 // Implementation for hcl.ExprMap.
599 func (e *expression) ExprMap() []hcl.KeyValuePair {
600 switch v := e.src.(type) {
602 ret := make([]hcl.KeyValuePair, len(v.Attrs))
603 for i, jsonAttr := range v.Attrs {
604 ret[i] = hcl.KeyValuePair{
605 Key: &expression{src: &stringVal{
606 Value: jsonAttr.Name,
607 SrcRange: jsonAttr.NameRange,
609 Value: &expression{src: jsonAttr.Value},