json: Add ParseExpression function
This commit is contained in:
parent
90676d47a0
commit
636e660fac
@ -23,6 +23,21 @@ func parseFileContent(buf []byte, filename string, start hcl.Pos) (node, hcl.Dia
|
||||
return node, diags
|
||||
}
|
||||
|
||||
func parseExpression(buf []byte, filename string, start hcl.Pos) (node, hcl.Diagnostics) {
|
||||
tokens := scan(buf, pos{Filename: filename, Pos: start})
|
||||
p := newPeeker(tokens)
|
||||
node, diags := parseValue(p)
|
||||
if len(diags) == 0 && p.Peek().Type != tokenEOF {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Extraneous data after value",
|
||||
Detail: "Extra characters appear after the JSON value.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
return node, diags
|
||||
}
|
||||
|
||||
func parseValue(p *peeker) (node, hcl.Diagnostics) {
|
||||
tok := p.Peek()
|
||||
|
||||
|
@ -71,6 +71,20 @@ func ParseWithStartPos(src []byte, filename string, start hcl.Pos) (*hcl.File, h
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseExpression parses the given buffer as a standalone JSON expression,
|
||||
// returning it as an instance of Expression.
|
||||
func ParseExpression(src []byte, filename string) (hcl.Expression, hcl.Diagnostics) {
|
||||
return ParseExpressionWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
|
||||
}
|
||||
|
||||
// ParseExpressionWithStartPos parses like json.ParseExpression, but unlike
|
||||
// json.ParseExpression you can pass a start position of the given JSON
|
||||
// expression as a hcl.Pos.
|
||||
func ParseExpressionWithStartPos(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) {
|
||||
node, diags := parseExpression(src, filename, start)
|
||||
return &expression{src: node}, diags
|
||||
}
|
||||
|
||||
// ParseFile is a convenience wrapper around Parse that first attempts to load
|
||||
// data from the given filename, passing the result to Parse if successful.
|
||||
//
|
||||
|
@ -1,6 +1,7 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -182,3 +183,137 @@ func TestParseWithStartPos(t *testing.T) {
|
||||
t.Errorf("The two ranges did not match: src=%s, part=%s", srcRange, partRange)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input string
|
||||
Want string
|
||||
}{
|
||||
{
|
||||
`"hello"`,
|
||||
`cty.StringVal("hello")`,
|
||||
},
|
||||
{
|
||||
`"hello ${noun}"`,
|
||||
`cty.StringVal("hello world")`,
|
||||
},
|
||||
{
|
||||
"true",
|
||||
"cty.True",
|
||||
},
|
||||
{
|
||||
"false",
|
||||
"cty.False",
|
||||
},
|
||||
{
|
||||
"1",
|
||||
"cty.NumberIntVal(1)",
|
||||
},
|
||||
{
|
||||
"{}",
|
||||
"cty.EmptyObjectVal",
|
||||
},
|
||||
{
|
||||
`{"foo":"bar","baz":1}`,
|
||||
`cty.ObjectVal(map[string]cty.Value{"baz":cty.NumberIntVal(1), "foo":cty.StringVal("bar")})`,
|
||||
},
|
||||
{
|
||||
"[]",
|
||||
"cty.EmptyTupleVal",
|
||||
},
|
||||
{
|
||||
`["1",2,3]`,
|
||||
`cty.TupleVal([]cty.Value{cty.StringVal("1"), cty.NumberIntVal(2), cty.NumberIntVal(3)})`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Input, func(t *testing.T) {
|
||||
expr, diags := ParseExpression([]byte(test.Input), "")
|
||||
if diags.HasErrors() {
|
||||
t.Errorf("got %d diagnostics; want 0", len(diags))
|
||||
for _, d := range diags {
|
||||
t.Logf(" - %s", d.Error())
|
||||
}
|
||||
}
|
||||
|
||||
value, diags := expr.Value(&hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"noun": cty.StringVal("world"),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Errorf("got %d diagnostics on decode value; want 0", len(diags))
|
||||
for _, d := range diags {
|
||||
t.Logf(" - %s", d.Error())
|
||||
}
|
||||
}
|
||||
got := fmt.Sprintf("%#v", value)
|
||||
|
||||
if got != test.Want {
|
||||
t.Errorf("got %s, but want %s", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExpression_malformed(t *testing.T) {
|
||||
src := `invalid`
|
||||
expr, diags := ParseExpression([]byte(src), "")
|
||||
if got, want := len(diags), 1; got != want {
|
||||
t.Errorf("got %d diagnostics; want %d", got, want)
|
||||
}
|
||||
if err, want := diags.Error(), `Invalid JSON keyword`; !strings.Contains(err, want) {
|
||||
t.Errorf("diags are %q, but should contain %q", err, want)
|
||||
}
|
||||
if expr == nil {
|
||||
t.Errorf("got nil Expression; want actual expression")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExpressionWithStartPos(t *testing.T) {
|
||||
src := `{
|
||||
"foo": "bar"
|
||||
}`
|
||||
part := `"bar"`
|
||||
|
||||
file, diags := Parse([]byte(src), "")
|
||||
partExpr, partDiags := ParseExpressionWithStartPos([]byte(part), "", hcl.Pos{Byte: 0, Line: 2, Column: 10})
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("got %d diagnostics on parse src; want 0", len(diags))
|
||||
for _, diag := range diags {
|
||||
t.Logf("- %s", diag.Error())
|
||||
}
|
||||
}
|
||||
if len(partDiags) != 0 {
|
||||
t.Errorf("got %d diagnostics on parse part src; want 0", len(partDiags))
|
||||
for _, diag := range partDiags {
|
||||
t.Logf("- %s", diag.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if file == nil {
|
||||
t.Errorf("got nil File; want actual file")
|
||||
}
|
||||
if file.Body == nil {
|
||||
t.Errorf("got nil Body: want actual body")
|
||||
}
|
||||
if partExpr == nil {
|
||||
t.Errorf("got nil Expression; want actual expression")
|
||||
}
|
||||
|
||||
content, diags := file.Body.Content(&hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{{Name: "foo"}},
|
||||
})
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("got %d diagnostics on decode; want 0", len(diags))
|
||||
for _, diag := range diags {
|
||||
t.Logf("- %s", diag.Error())
|
||||
}
|
||||
}
|
||||
expr := content.Attributes["foo"].Expr
|
||||
|
||||
if expr.Range().String() != partExpr.Range().String() {
|
||||
t.Errorf("The two ranges did not match: src=%s, part=%s", expr.Range(), partExpr.Range())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user