2017-05-18 14:57:04 +00:00
|
|
|
package json
|
|
|
|
|
|
|
|
import (
|
2017-05-19 15:42:11 +00:00
|
|
|
"fmt"
|
|
|
|
|
2019-09-09 23:08:19 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2017-05-28 00:35:44 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2018-02-17 18:26:58 +00:00
|
|
|
"github.com/zclconf/go-cty/cty/convert"
|
2017-05-18 14:57:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// body is the implementation of "Body" used for files processed with the JSON
|
|
|
|
// parser.
|
|
|
|
type body struct {
|
2018-02-17 18:26:58 +00:00
|
|
|
val node
|
2017-05-18 14:57:04 +00:00
|
|
|
|
|
|
|
// If non-nil, the keys of this map cause the corresponding attributes to
|
|
|
|
// be treated as non-existing. This is used when Body.PartialContent is
|
|
|
|
// called, to produce the "remaining content" Body.
|
|
|
|
hiddenAttrs map[string]struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// expression is the implementation of "Expression" used for files processed
|
|
|
|
// with the JSON parser.
|
|
|
|
type expression struct {
|
2017-09-11 22:46:01 +00:00
|
|
|
src node
|
2017-05-18 14:57:04 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
2017-05-20 16:50:48 +00:00
|
|
|
content, newBody, diags := b.PartialContent(schema)
|
2017-05-19 15:42:11 +00:00
|
|
|
|
2017-05-20 16:50:48 +00:00
|
|
|
hiddenAttrs := newBody.(*body).hiddenAttrs
|
|
|
|
|
|
|
|
var nameSuggestions []string
|
|
|
|
for _, attrS := range schema.Attributes {
|
|
|
|
if _, ok := hiddenAttrs[attrS.Name]; !ok {
|
|
|
|
// Only suggest an attribute name if we didn't use it already.
|
|
|
|
nameSuggestions = append(nameSuggestions, attrS.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, blockS := range schema.Blocks {
|
|
|
|
// Blocks can appear multiple times, so we'll suggest their type
|
|
|
|
// names regardless of whether they've already been used.
|
|
|
|
nameSuggestions = append(nameSuggestions, blockS.Type)
|
|
|
|
}
|
|
|
|
|
2018-02-17 18:26:58 +00:00
|
|
|
jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
|
|
|
|
diags = append(diags, attrDiags...)
|
|
|
|
|
|
|
|
for _, attr := range jsonAttrs {
|
|
|
|
k := attr.Name
|
2017-06-17 17:17:05 +00:00
|
|
|
if k == "//" {
|
|
|
|
// Ignore "//" keys in objects representing bodies, to allow
|
|
|
|
// their use as comments.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-05-20 16:50:48 +00:00
|
|
|
if _, ok := hiddenAttrs[k]; !ok {
|
|
|
|
suggestion := nameSuggestion(k, nameSuggestions)
|
|
|
|
if suggestion != "" {
|
2018-02-17 18:26:58 +00:00
|
|
|
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
2017-05-20 16:50:48 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-05-20 16:50:48 +00:00
|
|
|
Summary: "Extraneous JSON object property",
|
2018-07-18 22:41:35 +00:00
|
|
|
Detail: fmt.Sprintf("No argument or block type is named %q.%s", k, suggestion),
|
2017-05-20 16:50:48 +00:00
|
|
|
Subject: &attr.NameRange,
|
|
|
|
Context: attr.Range().Ptr(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2017-05-19 15:42:11 +00:00
|
|
|
|
|
|
|
return content, diags
|
2017-05-18 14:57:04 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
2018-02-17 18:26:58 +00:00
|
|
|
var diags hcl.Diagnostics
|
|
|
|
|
|
|
|
jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
|
|
|
|
diags = append(diags, attrDiags...)
|
2017-05-19 15:42:11 +00:00
|
|
|
|
|
|
|
usedNames := map[string]struct{}{}
|
|
|
|
if b.hiddenAttrs != nil {
|
|
|
|
for k := range b.hiddenAttrs {
|
|
|
|
usedNames[k] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
content := &hcl.BodyContent{
|
|
|
|
Attributes: map[string]*hcl.Attribute{},
|
2017-05-19 15:42:11 +00:00
|
|
|
Blocks: nil,
|
2017-06-03 23:46:49 +00:00
|
|
|
|
|
|
|
MissingItemRange: b.MissingItemRange(),
|
2017-05-19 15:42:11 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 18:26:58 +00:00
|
|
|
// Create some more convenient data structures for our work below.
|
|
|
|
attrSchemas := map[string]hcl.AttributeSchema{}
|
|
|
|
blockSchemas := map[string]hcl.BlockHeaderSchema{}
|
2017-05-19 15:42:11 +00:00
|
|
|
for _, attrS := range schema.Attributes {
|
2018-02-17 18:26:58 +00:00
|
|
|
attrSchemas[attrS.Name] = attrS
|
|
|
|
}
|
|
|
|
for _, blockS := range schema.Blocks {
|
|
|
|
blockSchemas[blockS.Type] = blockS
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, jsonAttr := range jsonAttrs {
|
|
|
|
attrName := jsonAttr.Name
|
|
|
|
if _, used := b.hiddenAttrs[attrName]; used {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if attrS, defined := attrSchemas[attrName]; defined {
|
|
|
|
if existing, exists := content.Attributes[attrName]; exists {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
2017-09-11 23:40:37 +00:00
|
|
|
Severity: hcl.DiagError,
|
2018-07-18 22:41:35 +00:00
|
|
|
Summary: "Duplicate argument",
|
|
|
|
Detail: fmt.Sprintf("The argument %q was already set at %s.", attrName, existing.Range),
|
2018-02-17 18:26:58 +00:00
|
|
|
Subject: &jsonAttr.NameRange,
|
|
|
|
Context: jsonAttr.Range().Ptr(),
|
2017-05-19 15:42:11 +00:00
|
|
|
})
|
2018-02-17 18:26:58 +00:00
|
|
|
continue
|
2017-05-19 15:42:11 +00:00
|
|
|
}
|
2018-02-17 18:26:58 +00:00
|
|
|
|
|
|
|
content.Attributes[attrS.Name] = &hcl.Attribute{
|
|
|
|
Name: attrS.Name,
|
|
|
|
Expr: &expression{src: jsonAttr.Value},
|
|
|
|
Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
|
|
|
NameRange: jsonAttr.NameRange,
|
|
|
|
}
|
|
|
|
usedNames[attrName] = struct{}{}
|
|
|
|
|
|
|
|
} else if blockS, defined := blockSchemas[attrName]; defined {
|
|
|
|
bv := jsonAttr.Value
|
|
|
|
blockDiags := b.unpackBlock(bv, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks)
|
|
|
|
diags = append(diags, blockDiags...)
|
|
|
|
usedNames[attrName] = struct{}{}
|
2017-05-19 15:42:11 +00:00
|
|
|
}
|
2018-02-17 18:26:58 +00:00
|
|
|
|
|
|
|
// We ignore anything that isn't defined because that's the
|
|
|
|
// PartialContent contract. The Content method will catch leftovers.
|
2017-05-19 15:42:11 +00:00
|
|
|
}
|
|
|
|
|
2018-02-17 18:26:58 +00:00
|
|
|
// Make sure we got all the required attributes.
|
|
|
|
for _, attrS := range schema.Attributes {
|
|
|
|
if !attrS.Required {
|
2017-05-19 15:42:11 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-02-17 18:26:58 +00:00
|
|
|
if _, defined := content.Attributes[attrS.Name]; !defined {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2018-07-18 22:41:35 +00:00
|
|
|
Summary: "Missing required argument",
|
|
|
|
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
|
2018-02-17 18:26:58 +00:00
|
|
|
Subject: b.MissingItemRange().Ptr(),
|
|
|
|
})
|
|
|
|
}
|
2017-05-19 15:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unusedBody := &body{
|
2018-02-17 18:26:58 +00:00
|
|
|
val: b.val,
|
2017-05-19 15:42:11 +00:00
|
|
|
hiddenAttrs: usedNames,
|
|
|
|
}
|
|
|
|
|
|
|
|
return content, unusedBody, diags
|
|
|
|
}
|
|
|
|
|
2017-05-20 21:35:19 +00:00
|
|
|
// JustAttributes for JSON bodies interprets all properties of the wrapped
|
|
|
|
// JSON object as attributes and returns them.
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
2018-02-17 18:26:58 +00:00
|
|
|
var diags hcl.Diagnostics
|
2017-09-11 23:40:37 +00:00
|
|
|
attrs := make(map[string]*hcl.Attribute)
|
2018-02-17 18:26:58 +00:00
|
|
|
|
|
|
|
obj, ok := b.val.(*objectVal)
|
|
|
|
if !ok {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Incorrect JSON value type",
|
2018-07-18 22:41:35 +00:00
|
|
|
Detail: "A JSON object is required here, setting the arguments for this block.",
|
2018-02-17 18:26:58 +00:00
|
|
|
Subject: b.val.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
return attrs, diags
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, jsonAttr := range obj.Attrs {
|
|
|
|
name := jsonAttr.Name
|
2017-06-17 17:17:05 +00:00
|
|
|
if name == "//" {
|
|
|
|
// Ignore "//" keys in objects representing bodies, to allow
|
|
|
|
// their use as comments.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-05-21 16:46:50 +00:00
|
|
|
if _, hidden := b.hiddenAttrs[name]; hidden {
|
|
|
|
continue
|
|
|
|
}
|
2018-02-17 23:22:27 +00:00
|
|
|
|
|
|
|
if existing, exists := attrs[name]; exists {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Duplicate attribute definition",
|
2018-07-18 22:41:35 +00:00
|
|
|
Detail: fmt.Sprintf("The argument %q was already set at %s.", name, existing.Range),
|
2018-02-17 23:22:27 +00:00
|
|
|
Subject: &jsonAttr.NameRange,
|
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
attrs[name] = &hcl.Attribute{
|
2017-05-20 21:35:19 +00:00
|
|
|
Name: name,
|
2017-09-11 22:46:01 +00:00
|
|
|
Expr: &expression{src: jsonAttr.Value},
|
2017-09-11 23:40:37 +00:00
|
|
|
Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
2017-05-20 21:35:19 +00:00
|
|
|
NameRange: jsonAttr.NameRange,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No diagnostics possible here, since the parser already took care of
|
|
|
|
// finding duplicates and every JSON value can be a valid attribute value.
|
2018-02-17 18:26:58 +00:00
|
|
|
return attrs, diags
|
2017-05-20 21:35:19 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *body) MissingItemRange() hcl.Range {
|
2018-02-17 18:26:58 +00:00
|
|
|
switch tv := b.val.(type) {
|
|
|
|
case *objectVal:
|
|
|
|
return tv.CloseRange
|
|
|
|
case *arrayVal:
|
|
|
|
return tv.OpenRange
|
|
|
|
default:
|
|
|
|
// Should not happen in correct operation, but might show up if the
|
|
|
|
// input is invalid and we are producing partial results.
|
|
|
|
return tv.StartRange()
|
|
|
|
}
|
2017-05-21 18:46:58 +00:00
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labelsLeft []string, labelsUsed []string, labelRanges []hcl.Range, blocks *hcl.Blocks) (diags hcl.Diagnostics) {
|
2017-05-19 15:42:11 +00:00
|
|
|
if len(labelsLeft) > 0 {
|
|
|
|
labelName := labelsLeft[0]
|
2018-02-17 18:26:58 +00:00
|
|
|
jsonAttrs, attrDiags := b.collectDeepAttrs(v, &labelName)
|
|
|
|
diags = append(diags, attrDiags...)
|
|
|
|
|
|
|
|
if len(jsonAttrs) == 0 {
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-05-21 19:42:42 +00:00
|
|
|
Summary: "Missing block label",
|
|
|
|
Detail: fmt.Sprintf("At least one object property is required, whose name represents the %s block's %s.", typeName, labelName),
|
2017-05-19 15:42:11 +00:00
|
|
|
Subject: v.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
labelsUsed := append(labelsUsed, "")
|
2017-09-11 23:40:37 +00:00
|
|
|
labelRanges := append(labelRanges, hcl.Range{})
|
2018-02-17 18:26:58 +00:00
|
|
|
for _, p := range jsonAttrs {
|
|
|
|
pk := p.Name
|
2017-05-19 15:42:11 +00:00
|
|
|
labelsUsed[len(labelsUsed)-1] = pk
|
|
|
|
labelRanges[len(labelRanges)-1] = p.NameRange
|
|
|
|
diags = append(diags, b.unpackBlock(p.Value, typeName, typeRange, labelsLeft[1:], labelsUsed, labelRanges, blocks)...)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// By the time we get here, we've peeled off all the labels and we're ready
|
|
|
|
// to deal with the block's actual content.
|
|
|
|
|
|
|
|
// need to copy the label slices because their underlying arrays will
|
|
|
|
// continue to be mutated after we return.
|
|
|
|
labels := make([]string, len(labelsUsed))
|
|
|
|
copy(labels, labelsUsed)
|
2017-09-11 23:40:37 +00:00
|
|
|
labelR := make([]hcl.Range, len(labelRanges))
|
2017-05-19 15:42:11 +00:00
|
|
|
copy(labelR, labelRanges)
|
|
|
|
|
|
|
|
switch tv := v.(type) {
|
2019-02-14 11:58:25 +00:00
|
|
|
case *nullVal:
|
|
|
|
// There is no block content, e.g the value is null.
|
|
|
|
return
|
2017-05-19 15:42:11 +00:00
|
|
|
case *objectVal:
|
|
|
|
// Single instance of the block
|
2017-09-11 23:40:37 +00:00
|
|
|
*blocks = append(*blocks, &hcl.Block{
|
2017-05-19 15:42:11 +00:00
|
|
|
Type: typeName,
|
|
|
|
Labels: labels,
|
|
|
|
Body: &body{
|
2018-02-17 18:26:58 +00:00
|
|
|
val: tv,
|
2017-05-19 15:42:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
DefRange: tv.OpenRange,
|
|
|
|
TypeRange: *typeRange,
|
|
|
|
LabelRanges: labelR,
|
|
|
|
})
|
|
|
|
case *arrayVal:
|
|
|
|
// Multiple instances of the block
|
|
|
|
for _, av := range tv.Values {
|
2017-09-11 23:40:37 +00:00
|
|
|
*blocks = append(*blocks, &hcl.Block{
|
2017-05-19 15:42:11 +00:00
|
|
|
Type: typeName,
|
|
|
|
Labels: labels,
|
|
|
|
Body: &body{
|
2018-02-17 18:26:58 +00:00
|
|
|
val: av, // might be mistyped; we'll find out when content is requested for this body
|
2017-05-19 15:42:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
DefRange: tv.OpenRange,
|
|
|
|
TypeRange: *typeRange,
|
|
|
|
LabelRanges: labelR,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
default:
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-05-19 15:42:11 +00:00
|
|
|
Summary: "Incorrect JSON value type",
|
|
|
|
Detail: fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName),
|
|
|
|
Subject: v.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-17 18:26:58 +00:00
|
|
|
// collectDeepAttrs takes either a single object or an array of objects and
|
|
|
|
// flattens it into a list of object attributes, collecting attributes from
|
|
|
|
// all of the objects in a given array.
|
|
|
|
//
|
|
|
|
// Ordering is preserved, so a list of objects that each have one property
|
|
|
|
// will result in those properties being returned in the same order as the
|
|
|
|
// objects appeared in the array.
|
|
|
|
//
|
|
|
|
// This is appropriate for use only for objects representing bodies or labels
|
|
|
|
// within a block.
|
|
|
|
//
|
|
|
|
// The labelName argument, if non-null, is used to tailor returned error
|
|
|
|
// messages to refer to block labels rather than attributes and child blocks.
|
|
|
|
// It has no other effect.
|
|
|
|
func (b *body) collectDeepAttrs(v node, labelName *string) ([]*objectAttr, hcl.Diagnostics) {
|
|
|
|
var diags hcl.Diagnostics
|
|
|
|
var attrs []*objectAttr
|
|
|
|
|
|
|
|
switch tv := v.(type) {
|
2019-02-14 11:58:25 +00:00
|
|
|
case *nullVal:
|
|
|
|
// If a value is null, then we don't return any attributes or return an error.
|
2018-02-17 18:26:58 +00:00
|
|
|
|
|
|
|
case *objectVal:
|
|
|
|
attrs = append(attrs, tv.Attrs...)
|
|
|
|
|
|
|
|
case *arrayVal:
|
|
|
|
for _, ev := range tv.Values {
|
|
|
|
switch tev := ev.(type) {
|
|
|
|
case *objectVal:
|
|
|
|
attrs = append(attrs, tev.Attrs...)
|
|
|
|
default:
|
|
|
|
if labelName != nil {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Incorrect JSON value type",
|
|
|
|
Detail: fmt.Sprintf("A JSON object is required here, to specify %s labels for this block.", *labelName),
|
|
|
|
Subject: ev.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Incorrect JSON value type",
|
2018-07-18 22:41:35 +00:00
|
|
|
Detail: "A JSON object is required here, to define arguments and child blocks.",
|
2018-02-17 18:26:58 +00:00
|
|
|
Subject: ev.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
if labelName != nil {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Incorrect JSON value type",
|
|
|
|
Detail: fmt.Sprintf("Either a JSON object or JSON array of objects is required here, to specify %s labels for this block.", *labelName),
|
|
|
|
Subject: v.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Incorrect JSON value type",
|
2018-07-18 22:41:35 +00:00
|
|
|
Detail: "Either a JSON object or JSON array of objects is required here, to define arguments and child blocks.",
|
2018-02-17 18:26:58 +00:00
|
|
|
Subject: v.StartRange().Ptr(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrs, diags
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
2017-05-20 22:17:56 +00:00
|
|
|
switch v := e.src.(type) {
|
|
|
|
case *stringVal:
|
2017-06-12 14:25:09 +00:00
|
|
|
if ctx != nil {
|
2018-01-27 18:51:26 +00:00
|
|
|
// Parse string contents as a HCL native language expression.
|
2017-06-12 14:25:09 +00:00
|
|
|
// We only do this if we have a context, so passing a nil context
|
|
|
|
// is how the caller specifies that interpolations are not allowed
|
|
|
|
// and that the string should just be returned verbatim.
|
|
|
|
templateSrc := v.Value
|
2017-09-11 23:40:37 +00:00
|
|
|
expr, diags := hclsyntax.ParseTemplate(
|
2017-06-12 14:25:09 +00:00
|
|
|
[]byte(templateSrc),
|
|
|
|
v.SrcRange.Filename,
|
|
|
|
|
|
|
|
// This won't produce _exactly_ the right result, since
|
2018-01-24 05:54:38 +00:00
|
|
|
// the hclsyntax parser can't "see" any escapes we removed
|
2017-06-12 14:25:09 +00:00
|
|
|
// while parsing JSON, but it's better than nothing.
|
2017-09-11 23:40:37 +00:00
|
|
|
hcl.Pos{
|
2017-06-12 14:25:09 +00:00
|
|
|
Line: v.SrcRange.Start.Line,
|
|
|
|
|
|
|
|
// skip over the opening quote mark
|
|
|
|
Byte: v.SrcRange.Start.Byte + 1,
|
|
|
|
Column: v.SrcRange.Start.Column + 1,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return cty.DynamicVal, diags
|
|
|
|
}
|
|
|
|
val, evalDiags := expr.Value(ctx)
|
|
|
|
diags = append(diags, evalDiags...)
|
|
|
|
return val, diags
|
|
|
|
}
|
|
|
|
|
2017-05-20 22:17:56 +00:00
|
|
|
return cty.StringVal(v.Value), nil
|
|
|
|
case *numberVal:
|
|
|
|
return cty.NumberVal(v.Value), nil
|
|
|
|
case *booleanVal:
|
|
|
|
return cty.BoolVal(v.Value), nil
|
|
|
|
case *arrayVal:
|
2019-06-17 16:00:22 +00:00
|
|
|
var diags hcl.Diagnostics
|
2017-05-20 22:17:56 +00:00
|
|
|
vals := []cty.Value{}
|
|
|
|
for _, jsonVal := range v.Values {
|
2019-06-17 16:00:22 +00:00
|
|
|
val, valDiags := (&expression{src: jsonVal}).Value(ctx)
|
2017-05-20 22:17:56 +00:00
|
|
|
vals = append(vals, val)
|
2019-06-17 16:00:22 +00:00
|
|
|
diags = append(diags, valDiags...)
|
2017-05-20 22:17:56 +00:00
|
|
|
}
|
2019-06-17 16:00:22 +00:00
|
|
|
return cty.TupleVal(vals), diags
|
2017-05-20 22:17:56 +00:00
|
|
|
case *objectVal:
|
2018-02-17 18:26:58 +00:00
|
|
|
var diags hcl.Diagnostics
|
2017-05-20 22:17:56 +00:00
|
|
|
attrs := map[string]cty.Value{}
|
2018-02-17 18:26:58 +00:00
|
|
|
attrRanges := map[string]hcl.Range{}
|
|
|
|
known := true
|
|
|
|
for _, jsonAttr := range v.Attrs {
|
|
|
|
// In this one context we allow keys to contain interpolation
|
2018-12-13 00:49:01 +00:00
|
|
|
// expressions too, assuming we're evaluating in interpolation
|
2018-02-17 18:26:58 +00:00
|
|
|
// mode. This achieves parity with the native syntax where
|
|
|
|
// object expressions can have dynamic keys, while block contents
|
|
|
|
// may not.
|
|
|
|
name, nameDiags := (&expression{src: &stringVal{
|
|
|
|
Value: jsonAttr.Name,
|
|
|
|
SrcRange: jsonAttr.NameRange,
|
|
|
|
}}).Value(ctx)
|
2018-07-28 20:36:55 +00:00
|
|
|
valExpr := &expression{src: jsonAttr.Value}
|
|
|
|
val, valDiags := valExpr.Value(ctx)
|
2018-02-17 18:26:58 +00:00
|
|
|
diags = append(diags, nameDiags...)
|
|
|
|
diags = append(diags, valDiags...)
|
|
|
|
|
|
|
|
var err error
|
|
|
|
name, err = convert.Convert(name, cty.String)
|
|
|
|
if err != nil {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
2018-07-28 20:14:36 +00:00
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Invalid object key expression",
|
|
|
|
Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err),
|
|
|
|
Subject: &jsonAttr.NameRange,
|
2018-07-28 20:36:55 +00:00
|
|
|
Expression: valExpr,
|
2018-07-28 20:14:36 +00:00
|
|
|
EvalContext: ctx,
|
2018-02-17 18:26:58 +00:00
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if name.IsNull() {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
2018-07-28 20:14:36 +00:00
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Invalid object key expression",
|
|
|
|
Detail: "Cannot use null value as an object key.",
|
|
|
|
Subject: &jsonAttr.NameRange,
|
2018-07-28 20:36:55 +00:00
|
|
|
Expression: valExpr,
|
2018-07-28 20:14:36 +00:00
|
|
|
EvalContext: ctx,
|
2018-02-17 18:26:58 +00:00
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !name.IsKnown() {
|
|
|
|
// This is a bit of a weird case, since our usual rules require
|
|
|
|
// us to tolerate unknowns and just represent the result as
|
|
|
|
// best we can but if we don't know the key then we can't
|
|
|
|
// know the type of our object at all, and thus we must turn
|
|
|
|
// the whole thing into cty.DynamicVal. This is consistent with
|
|
|
|
// how this situation is handled in the native syntax.
|
|
|
|
// We'll keep iterating so we can collect other errors in
|
|
|
|
// subsequent attributes.
|
|
|
|
known = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
nameStr := name.AsString()
|
|
|
|
if _, defined := attrs[nameStr]; defined {
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
2018-07-28 20:14:36 +00:00
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Duplicate object attribute",
|
|
|
|
Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]),
|
|
|
|
Subject: &jsonAttr.NameRange,
|
2018-07-28 20:36:55 +00:00
|
|
|
Expression: e,
|
2018-07-28 20:14:36 +00:00
|
|
|
EvalContext: ctx,
|
2018-02-17 18:26:58 +00:00
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
attrs[nameStr] = val
|
|
|
|
attrRanges[nameStr] = jsonAttr.NameRange
|
|
|
|
}
|
|
|
|
if !known {
|
|
|
|
// We encountered an unknown key somewhere along the way, so
|
|
|
|
// we can't know what our type will eventually be.
|
|
|
|
return cty.DynamicVal, diags
|
2017-05-20 22:17:56 +00:00
|
|
|
}
|
2018-02-17 18:26:58 +00:00
|
|
|
return cty.ObjectVal(attrs), diags
|
2019-03-05 17:45:54 +00:00
|
|
|
case *nullVal:
|
|
|
|
return cty.NullVal(cty.DynamicPseudoType), nil
|
2017-05-20 22:17:56 +00:00
|
|
|
default:
|
|
|
|
// Default to DynamicVal so that ASTs containing invalid nodes can
|
|
|
|
// still be partially-evaluated.
|
|
|
|
return cty.DynamicVal, nil
|
|
|
|
}
|
2017-05-18 14:57:04 +00:00
|
|
|
}
|
2017-05-21 00:40:03 +00:00
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (e *expression) Variables() []hcl.Traversal {
|
|
|
|
var vars []hcl.Traversal
|
2017-05-23 01:54:23 +00:00
|
|
|
|
|
|
|
switch v := e.src.(type) {
|
|
|
|
case *stringVal:
|
2018-01-13 07:35:58 +00:00
|
|
|
templateSrc := v.Value
|
|
|
|
expr, diags := hclsyntax.ParseTemplate(
|
|
|
|
[]byte(templateSrc),
|
|
|
|
v.SrcRange.Filename,
|
|
|
|
|
|
|
|
// This won't produce _exactly_ the right result, since
|
2018-01-24 05:54:38 +00:00
|
|
|
// the hclsyntax parser can't "see" any escapes we removed
|
2018-01-13 07:35:58 +00:00
|
|
|
// while parsing JSON, but it's better than nothing.
|
|
|
|
hcl.Pos{
|
|
|
|
Line: v.SrcRange.Start.Line,
|
|
|
|
|
|
|
|
// skip over the opening quote mark
|
|
|
|
Byte: v.SrcRange.Start.Byte + 1,
|
|
|
|
Column: v.SrcRange.Start.Column + 1,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return vars
|
|
|
|
}
|
|
|
|
return expr.Variables()
|
2017-05-23 01:54:23 +00:00
|
|
|
|
|
|
|
case *arrayVal:
|
|
|
|
for _, jsonVal := range v.Values {
|
2017-09-11 22:46:01 +00:00
|
|
|
vars = append(vars, (&expression{src: jsonVal}).Variables()...)
|
2017-05-23 01:54:23 +00:00
|
|
|
}
|
|
|
|
case *objectVal:
|
|
|
|
for _, jsonAttr := range v.Attrs {
|
2018-12-13 00:49:01 +00:00
|
|
|
keyExpr := &stringVal{ // we're going to treat key as an expression in this context
|
|
|
|
Value: jsonAttr.Name,
|
|
|
|
SrcRange: jsonAttr.NameRange,
|
|
|
|
}
|
|
|
|
vars = append(vars, (&expression{src: keyExpr}).Variables()...)
|
2017-09-11 22:46:01 +00:00
|
|
|
vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
|
2017-05-23 01:54:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return vars
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (e *expression) Range() hcl.Range {
|
2017-05-21 00:40:03 +00:00
|
|
|
return e.src.Range()
|
|
|
|
}
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
func (e *expression) StartRange() hcl.Range {
|
2017-05-21 00:40:03 +00:00
|
|
|
return e.src.StartRange()
|
|
|
|
}
|
2018-01-13 06:58:55 +00:00
|
|
|
|
|
|
|
// Implementation for hcl.AbsTraversalForExpr.
|
|
|
|
func (e *expression) AsTraversal() hcl.Traversal {
|
|
|
|
// In JSON-based syntax a traversal is given as a string containing
|
|
|
|
// traversal syntax as defined by hclsyntax.ParseTraversalAbs.
|
|
|
|
|
|
|
|
switch v := e.src.(type) {
|
|
|
|
case *stringVal:
|
|
|
|
traversal, diags := hclsyntax.ParseTraversalAbs([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return traversal
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2018-01-13 07:30:41 +00:00
|
|
|
|
2018-03-04 22:04:54 +00:00
|
|
|
// Implementation for hcl.ExprCall.
|
|
|
|
func (e *expression) ExprCall() *hcl.StaticCall {
|
|
|
|
// In JSON-based syntax a static call is given as a string containing
|
|
|
|
// an expression in the native syntax that also supports ExprCall.
|
|
|
|
|
|
|
|
switch v := e.src.(type) {
|
|
|
|
case *stringVal:
|
|
|
|
expr, diags := hclsyntax.ParseExpression([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
call, diags := hcl.ExprCall(expr)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return call
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 07:30:41 +00:00
|
|
|
// Implementation for hcl.ExprList.
|
|
|
|
func (e *expression) ExprList() []hcl.Expression {
|
|
|
|
switch v := e.src.(type) {
|
|
|
|
case *arrayVal:
|
|
|
|
ret := make([]hcl.Expression, len(v.Values))
|
|
|
|
for i, node := range v.Values {
|
|
|
|
ret[i] = &expression{src: node}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2018-02-23 16:41:58 +00:00
|
|
|
|
|
|
|
// Implementation for hcl.ExprMap.
|
|
|
|
func (e *expression) ExprMap() []hcl.KeyValuePair {
|
|
|
|
switch v := e.src.(type) {
|
|
|
|
case *objectVal:
|
|
|
|
ret := make([]hcl.KeyValuePair, len(v.Attrs))
|
|
|
|
for i, jsonAttr := range v.Attrs {
|
|
|
|
ret[i] = hcl.KeyValuePair{
|
|
|
|
Key: &expression{src: &stringVal{
|
|
|
|
Value: jsonAttr.Name,
|
|
|
|
SrcRange: jsonAttr.NameRange,
|
|
|
|
}},
|
|
|
|
Value: &expression{src: jsonAttr.Value},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|