1package json
2
3import (
4	"fmt"
5
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"
10)
11
12// body is the implementation of "Body" used for files processed with the JSON
13// parser.
14type body struct {
15	val node
16
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{}
21}
22
23// expression is the implementation of "Expression" used for files processed
24// with the JSON parser.
25type expression struct {
26	src node
27}
28
29func (b *body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
30	content, newBody, diags := b.PartialContent(schema)
31
32	hiddenAttrs := newBody.(*body).hiddenAttrs
33
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)
39		}
40	}
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)
45	}
46
47	jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
48	diags = append(diags, attrDiags...)
49
50	for _, attr := range jsonAttrs {
51		k := attr.Name
52		if k == "//" {
53			// Ignore "//" keys in objects representing bodies, to allow
54			// their use as comments.
55			continue
56		}
57
58		if _, ok := hiddenAttrs[k]; !ok {
59			suggestion := nameSuggestion(k, nameSuggestions)
60			if suggestion != "" {
61				suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
62			}
63
64			diags = append(diags, &hcl.Diagnostic{
65				Severity: hcl.DiagError,
66				Summary:  "Extraneous JSON object property",
67				Detail:   fmt.Sprintf("No argument or block type is named %q.%s", k, suggestion),
68				Subject:  &attr.NameRange,
69				Context:  attr.Range().Ptr(),
70			})
71		}
72	}
73
74	return content, diags
75}
76
77func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
78	var diags hcl.Diagnostics
79
80	jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
81	diags = append(diags, attrDiags...)
82
83	usedNames := map[string]struct{}{}
84	if b.hiddenAttrs != nil {
85		for k := range b.hiddenAttrs {
86			usedNames[k] = struct{}{}
87		}
88	}
89
90	content := &hcl.BodyContent{
91		Attributes: map[string]*hcl.Attribute{},
92		Blocks:     nil,
93
94		MissingItemRange: b.MissingItemRange(),
95	}
96
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
102	}
103	for _, blockS := range schema.Blocks {
104		blockSchemas[blockS.Type] = blockS
105	}
106
107	for _, jsonAttr := range jsonAttrs {
108		attrName := jsonAttr.Name
109		if _, used := b.hiddenAttrs[attrName]; used {
110			continue
111		}
112
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 argument",
118					Detail:   fmt.Sprintf("The argument %q was already set at %s.", attrName, existing.Range),
119					Subject:  &jsonAttr.NameRange,
120					Context:  jsonAttr.Range().Ptr(),
121				})
122				continue
123			}
124
125			content.Attributes[attrS.Name] = &hcl.Attribute{
126				Name:      attrS.Name,
127				Expr:      &expression{src: jsonAttr.Value},
128				Range:     hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
129				NameRange: jsonAttr.NameRange,
130			}
131			usedNames[attrName] = struct{}{}
132
133		} else if blockS, defined := blockSchemas[attrName]; defined {
134			bv := jsonAttr.Value
135			blockDiags := b.unpackBlock(bv, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks)
136			diags = append(diags, blockDiags...)
137			usedNames[attrName] = struct{}{}
138		}
139
140		// We ignore anything that isn't defined because that's the
141		// PartialContent contract. The Content method will catch leftovers.
142	}
143
144	// Make sure we got all the required attributes.
145	for _, attrS := range schema.Attributes {
146		if !attrS.Required {
147			continue
148		}
149		if _, defined := content.Attributes[attrS.Name]; !defined {
150			diags = append(diags, &hcl.Diagnostic{
151				Severity: hcl.DiagError,
152				Summary:  "Missing required argument",
153				Detail:   fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
154				Subject:  b.MissingItemRange().Ptr(),
155			})
156		}
157	}
158
159	unusedBody := &body{
160		val:         b.val,
161		hiddenAttrs: usedNames,
162	}
163
164	return content, unusedBody, diags
165}
166
167// JustAttributes for JSON bodies interprets all properties of the wrapped
168// JSON object as attributes and returns them.
169func (b *body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
170	var diags hcl.Diagnostics
171	attrs := make(map[string]*hcl.Attribute)
172
173	obj, ok := b.val.(*objectVal)
174	if !ok {
175		diags = append(diags, &hcl.Diagnostic{
176			Severity: hcl.DiagError,
177			Summary:  "Incorrect JSON value type",
178			Detail:   "A JSON object is required here, setting the arguments for this block.",
179			Subject:  b.val.StartRange().Ptr(),
180		})
181		return attrs, diags
182	}
183
184	for _, jsonAttr := range obj.Attrs {
185		name := jsonAttr.Name
186		if name == "//" {
187			// Ignore "//" keys in objects representing bodies, to allow
188			// their use as comments.
189			continue
190		}
191
192		if _, hidden := b.hiddenAttrs[name]; hidden {
193			continue
194		}
195
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 argument %q was already set at %s.", name, existing.Range),
201				Subject:  &jsonAttr.NameRange,
202			})
203			continue
204		}
205
206		attrs[name] = &hcl.Attribute{
207			Name:      name,
208			Expr:      &expression{src: jsonAttr.Value},
209			Range:     hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
210			NameRange: jsonAttr.NameRange,
211		}
212	}
213
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.
216	return attrs, diags
217}
218
219func (b *body) MissingItemRange() hcl.Range {
220	switch tv := b.val.(type) {
221	case *objectVal:
222		return tv.CloseRange
223	case *arrayVal:
224		return tv.OpenRange
225	default:
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()
229	}
230}
231
232func (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...)
237
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(),
244			})
245			return
246		}
247		labelsUsed := append(labelsUsed, "")
248		labelRanges := append(labelRanges, hcl.Range{})
249		for _, p := range jsonAttrs {
250			pk := p.Name
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)...)
254		}
255		return
256	}
257
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.
260
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)
267
268	switch tv := v.(type) {
269	case *nullVal:
270		// There is no block content, e.g the value is null.
271		return
272	case *objectVal:
273		// Single instance of the block
274		*blocks = append(*blocks, &hcl.Block{
275			Type:   typeName,
276			Labels: labels,
277			Body: &body{
278				val: tv,
279			},
280
281			DefRange:    tv.OpenRange,
282			TypeRange:   *typeRange,
283			LabelRanges: labelR,
284		})
285	case *arrayVal:
286		// Multiple instances of the block
287		for _, av := range tv.Values {
288			*blocks = append(*blocks, &hcl.Block{
289				Type:   typeName,
290				Labels: labels,
291				Body: &body{
292					val: av, // might be mistyped; we'll find out when content is requested for this body
293				},
294
295				DefRange:    tv.OpenRange,
296				TypeRange:   *typeRange,
297				LabelRanges: labelR,
298			})
299		}
300	default:
301		diags = diags.Append(&hcl.Diagnostic{
302			Severity: hcl.DiagError,
303			Summary:  "Incorrect JSON value type",
304			Detail:   fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName),
305			Subject:  v.StartRange().Ptr(),
306		})
307	}
308	return
309}
310
311// collectDeepAttrs takes either a single object or an array of objects and
312// flattens it into a list of object attributes, collecting attributes from
313// all of the objects in a given array.
314//
315// Ordering is preserved, so a list of objects that each have one property
316// will result in those properties being returned in the same order as the
317// objects appeared in the array.
318//
319// This is appropriate for use only for objects representing bodies or labels
320// within a block.
321//
322// The labelName argument, if non-null, is used to tailor returned error
323// messages to refer to block labels rather than attributes and child blocks.
324// It has no other effect.
325func (b *body) collectDeepAttrs(v node, labelName *string) ([]*objectAttr, hcl.Diagnostics) {
326	var diags hcl.Diagnostics
327	var attrs []*objectAttr
328
329	switch tv := v.(type) {
330	case *nullVal:
331		// If a value is null, then we don't return any attributes or return an error.
332
333	case *objectVal:
334		attrs = append(attrs, tv.Attrs...)
335
336	case *arrayVal:
337		for _, ev := range tv.Values {
338			switch tev := ev.(type) {
339			case *objectVal:
340				attrs = append(attrs, tev.Attrs...)
341			default:
342				if labelName != nil {
343					diags = append(diags, &hcl.Diagnostic{
344						Severity: hcl.DiagError,
345						Summary:  "Incorrect JSON value type",
346						Detail:   fmt.Sprintf("A JSON object is required here, to specify %s labels for this block.", *labelName),
347						Subject:  ev.StartRange().Ptr(),
348					})
349				} else {
350					diags = append(diags, &hcl.Diagnostic{
351						Severity: hcl.DiagError,
352						Summary:  "Incorrect JSON value type",
353						Detail:   "A JSON object is required here, to define arguments and child blocks.",
354						Subject:  ev.StartRange().Ptr(),
355					})
356				}
357			}
358		}
359
360	default:
361		if labelName != nil {
362			diags = append(diags, &hcl.Diagnostic{
363				Severity: hcl.DiagError,
364				Summary:  "Incorrect JSON value type",
365				Detail:   fmt.Sprintf("Either a JSON object or JSON array of objects is required here, to specify %s labels for this block.", *labelName),
366				Subject:  v.StartRange().Ptr(),
367			})
368		} else {
369			diags = append(diags, &hcl.Diagnostic{
370				Severity: hcl.DiagError,
371				Summary:  "Incorrect JSON value type",
372				Detail:   "Either a JSON object or JSON array of objects is required here, to define arguments and child blocks.",
373				Subject:  v.StartRange().Ptr(),
374			})
375		}
376	}
377
378	return attrs, diags
379}
380
381func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
382	switch v := e.src.(type) {
383	case *stringVal:
384		if ctx != nil {
385			// Parse string contents as a HCL native language expression.
386			// We only do this if we have a context, so passing a nil context
387			// is how the caller specifies that interpolations are not allowed
388			// and that the string should just be returned verbatim.
389			templateSrc := v.Value
390			expr, diags := hclsyntax.ParseTemplate(
391				[]byte(templateSrc),
392				v.SrcRange.Filename,
393
394				// This won't produce _exactly_ the right result, since
395				// the hclsyntax parser can't "see" any escapes we removed
396				// while parsing JSON, but it's better than nothing.
397				hcl.Pos{
398					Line: v.SrcRange.Start.Line,
399
400					// skip over the opening quote mark
401					Byte:   v.SrcRange.Start.Byte + 1,
402					Column: v.SrcRange.Start.Column + 1,
403				},
404			)
405			if diags.HasErrors() {
406				return cty.DynamicVal, diags
407			}
408			val, evalDiags := expr.Value(ctx)
409			diags = append(diags, evalDiags...)
410			return val, diags
411		}
412
413		return cty.StringVal(v.Value), nil
414	case *numberVal:
415		return cty.NumberVal(v.Value), nil
416	case *booleanVal:
417		return cty.BoolVal(v.Value), nil
418	case *arrayVal:
419		var diags hcl.Diagnostics
420		vals := []cty.Value{}
421		for _, jsonVal := range v.Values {
422			val, valDiags := (&expression{src: jsonVal}).Value(ctx)
423			vals = append(vals, val)
424			diags = append(diags, valDiags...)
425		}
426		return cty.TupleVal(vals), diags
427	case *objectVal:
428		var diags hcl.Diagnostics
429		attrs := map[string]cty.Value{}
430		attrRanges := map[string]hcl.Range{}
431		known := true
432		for _, jsonAttr := range v.Attrs {
433			// In this one context we allow keys to contain interpolation
434			// expressions too, assuming we're evaluating in interpolation
435			// mode. This achieves parity with the native syntax where
436			// object expressions can have dynamic keys, while block contents
437			// may not.
438			name, nameDiags := (&expression{src: &stringVal{
439				Value:    jsonAttr.Name,
440				SrcRange: jsonAttr.NameRange,
441			}}).Value(ctx)
442			valExpr := &expression{src: jsonAttr.Value}
443			val, valDiags := valExpr.Value(ctx)
444			diags = append(diags, nameDiags...)
445			diags = append(diags, valDiags...)
446
447			var err error
448			name, err = convert.Convert(name, cty.String)
449			if err != nil {
450				diags = append(diags, &hcl.Diagnostic{
451					Severity:    hcl.DiagError,
452					Summary:     "Invalid object key expression",
453					Detail:      fmt.Sprintf("Cannot use this expression as an object key: %s.", err),
454					Subject:     &jsonAttr.NameRange,
455					Expression:  valExpr,
456					EvalContext: ctx,
457				})
458				continue
459			}
460			if name.IsNull() {
461				diags = append(diags, &hcl.Diagnostic{
462					Severity:    hcl.DiagError,
463					Summary:     "Invalid object key expression",
464					Detail:      "Cannot use null value as an object key.",
465					Subject:     &jsonAttr.NameRange,
466					Expression:  valExpr,
467					EvalContext: ctx,
468				})
469				continue
470			}
471			if !name.IsKnown() {
472				// This is a bit of a weird case, since our usual rules require
473				// us to tolerate unknowns and just represent the result as
474				// best we can but if we don't know the key then we can't
475				// know the type of our object at all, and thus we must turn
476				// the whole thing into cty.DynamicVal. This is consistent with
477				// how this situation is handled in the native syntax.
478				// We'll keep iterating so we can collect other errors in
479				// subsequent attributes.
480				known = false
481				continue
482			}
483			nameStr := name.AsString()
484			if _, defined := attrs[nameStr]; defined {
485				diags = append(diags, &hcl.Diagnostic{
486					Severity:    hcl.DiagError,
487					Summary:     "Duplicate object attribute",
488					Detail:      fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]),
489					Subject:     &jsonAttr.NameRange,
490					Expression:  e,
491					EvalContext: ctx,
492				})
493				continue
494			}
495			attrs[nameStr] = val
496			attrRanges[nameStr] = jsonAttr.NameRange
497		}
498		if !known {
499			// We encountered an unknown key somewhere along the way, so
500			// we can't know what our type will eventually be.
501			return cty.DynamicVal, diags
502		}
503		return cty.ObjectVal(attrs), diags
504	case *nullVal:
505		return cty.NullVal(cty.DynamicPseudoType), nil
506	default:
507		// Default to DynamicVal so that ASTs containing invalid nodes can
508		// still be partially-evaluated.
509		return cty.DynamicVal, nil
510	}
511}
512
513func (e *expression) Variables() []hcl.Traversal {
514	var vars []hcl.Traversal
515
516	switch v := e.src.(type) {
517	case *stringVal:
518		templateSrc := v.Value
519		expr, diags := hclsyntax.ParseTemplate(
520			[]byte(templateSrc),
521			v.SrcRange.Filename,
522
523			// This won't produce _exactly_ the right result, since
524			// the hclsyntax parser can't "see" any escapes we removed
525			// while parsing JSON, but it's better than nothing.
526			hcl.Pos{
527				Line: v.SrcRange.Start.Line,
528
529				// skip over the opening quote mark
530				Byte:   v.SrcRange.Start.Byte + 1,
531				Column: v.SrcRange.Start.Column + 1,
532			},
533		)
534		if diags.HasErrors() {
535			return vars
536		}
537		return expr.Variables()
538
539	case *arrayVal:
540		for _, jsonVal := range v.Values {
541			vars = append(vars, (&expression{src: jsonVal}).Variables()...)
542		}
543	case *objectVal:
544		for _, jsonAttr := range v.Attrs {
545			keyExpr := &stringVal{ // we're going to treat key as an expression in this context
546				Value:    jsonAttr.Name,
547				SrcRange: jsonAttr.NameRange,
548			}
549			vars = append(vars, (&expression{src: keyExpr}).Variables()...)
550			vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
551		}
552	}
553
554	return vars
555}
556
557func (e *expression) Range() hcl.Range {
558	return e.src.Range()
559}
560
561func (e *expression) StartRange() hcl.Range {
562	return e.src.StartRange()
563}
564
565// Implementation for hcl.AbsTraversalForExpr.
566func (e *expression) AsTraversal() hcl.Traversal {
567	// In JSON-based syntax a traversal is given as a string containing
568	// traversal syntax as defined by hclsyntax.ParseTraversalAbs.
569
570	switch v := e.src.(type) {
571	case *stringVal:
572		traversal, diags := hclsyntax.ParseTraversalAbs([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
573		if diags.HasErrors() {
574			return nil
575		}
576		return traversal
577	default:
578		return nil
579	}
580}
581
582// Implementation for hcl.ExprCall.
583func (e *expression) ExprCall() *hcl.StaticCall {
584	// In JSON-based syntax a static call is given as a string containing
585	// an expression in the native syntax that also supports ExprCall.
586
587	switch v := e.src.(type) {
588	case *stringVal:
589		expr, diags := hclsyntax.ParseExpression([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
590		if diags.HasErrors() {
591			return nil
592		}
593
594		call, diags := hcl.ExprCall(expr)
595		if diags.HasErrors() {
596			return nil
597		}
598
599		return call
600	default:
601		return nil
602	}
603}
604
605// Implementation for hcl.ExprList.
606func (e *expression) ExprList() []hcl.Expression {
607	switch v := e.src.(type) {
608	case *arrayVal:
609		ret := make([]hcl.Expression, len(v.Values))
610		for i, node := range v.Values {
611			ret[i] = &expression{src: node}
612		}
613		return ret
614	default:
615		return nil
616	}
617}
618
619// Implementation for hcl.ExprMap.
620func (e *expression) ExprMap() []hcl.KeyValuePair {
621	switch v := e.src.(type) {
622	case *objectVal:
623		ret := make([]hcl.KeyValuePair, len(v.Attrs))
624		for i, jsonAttr := range v.Attrs {
625			ret[i] = hcl.KeyValuePair{
626				Key: &expression{src: &stringVal{
627					Value:    jsonAttr.Name,
628					SrcRange: jsonAttr.NameRange,
629				}},
630				Value: &expression{src: jsonAttr.Value},
631			}
632		}
633		return ret
634	default:
635		return nil
636	}
637}
638