Skip to content

Commit

Permalink
ast: syntax errors for invalid fn invocation (#449)
Browse files Browse the repository at this point in the history
Currently when a fn expr is invalid we fall back to parsing it as an object, which results in an object containing a fn:: key, which is otherwise illegal, instead of the expected call. This PR makes these incorrect usages emit syntax errors so that it's easier for the user to identify the mistake ahead of time, instead of after discovering that the function wasn't called.

Case 1 - having an additional key in a fn call:
```
values:
  foo:
    fn::builtin: ...
    anotherKey: "oops"
```

Case 2 - having a fn:: key as a toplevel key in values:
```
values:
  fn::open::provider: ...
```

Resolves #447
---------

Co-authored-by: Pat Gavlin <[email protected]>
  • Loading branch information
nyobe and pgavlin authored Feb 20, 2025
1 parent 8648046 commit 6e2c31a
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@
### Bug Fixes

### Breaking changes

- It is now a syntax error to call a builtin function incorrectly.
[449](https://github.com/pulumi/esc/pull/449)
3 changes: 3 additions & 0 deletions ast/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ func (d *MapDecl[T]) parse(name string, node syntax.Node) syntax.Diagnostics {

var v T
vname := name + "." + kvp.Key.Value()
if strings.HasPrefix(kvp.Key.Value(), "fn::") {
diags.Extend(syntax.NodeError(kvp.Key, fmt.Sprintf("builtin function call %q not allowed at the top level", kvp.Key.Value())))
}
vdiags := parseNode(vname, &v, kvp.Value)
diags.Extend(vdiags...)

Expand Down
10 changes: 8 additions & 2 deletions ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,14 +625,20 @@ func FromBase64(value Expr) *FromBase64Expr {
}

func tryParseFunction(node *syntax.ObjectNode) (Expr, syntax.Diagnostics, bool) {
var diags syntax.Diagnostics
if node.Len() != 1 {
return nil, nil, false
for i := 0; i < node.Len(); i++ {
if k := node.Index(i).Key.Value(); strings.HasPrefix(k, "fn::") {
diags = append(diags, syntax.NodeError(node, fmt.Sprintf("illegal call to builtin function: %q must be the only key within its containing object", k)))

}
}
return nil, diags, false
}

kvp := node.Index(0)

var parse func(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics)
var diags syntax.Diagnostics
switch kvp.Key.Value() {
case "fn::fromJSON":
parse = parseFromJSON
Expand Down
6 changes: 6 additions & 0 deletions ast/testdata/parse/invalid-invocation/env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
values:
multiple-keys:
fn::rotate::provider:
inputs: {}
state:
ohno: true
78 changes: 78 additions & 0 deletions ast/testdata/parse/invalid-invocation/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"decl": {
"Description": null,
"Imports": null,
"Values": {
"Entries": [
{
"Key": {
"Value": "multiple-keys"
},
"Value": {
"Entries": [
{
"Key": {
"Value": "fn::rotate::provider"
},
"Value": {
"Entries": [
{
"Key": {
"Value": "inputs"
},
"Value": {
"Entries": []
}
}
]
}
},
{
"Key": {
"Value": "state"
},
"Value": {
"Entries": [
{
"Key": {
"Value": "ohno"
},
"Value": {
"Value": true
}
}
]
}
}
]
}
}
]
}
},
"diags": [
{
"Severity": 1,
"Summary": "illegal call to builtin function: \"fn::rotate::provider\" must be the only key within its containing object",
"Detail": "",
"Subject": {
"Filename": "invalid-invocation",
"Start": {
"Line": 3,
"Column": 5,
"Byte": 29
},
"End": {
"Line": 6,
"Column": 17,
"Byte": 95
}
},
"Context": null,
"Expression": null,
"EvalContext": null,
"Extra": null,
"Path": "values[\"multiple-keys\"]"
}
]
}
2 changes: 2 additions & 0 deletions ast/testdata/parse/invalid-invocation2/env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
values:
fn::secret: "hello"
43 changes: 43 additions & 0 deletions ast/testdata/parse/invalid-invocation2/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"decl": {
"Description": null,
"Imports": null,
"Values": {
"Entries": [
{
"Key": {
"Value": "fn::secret"
},
"Value": {
"Value": "hello"
}
}
]
}
},
"diags": [
{
"Severity": 1,
"Summary": "builtin function call \"fn::secret\" not allowed at the top level",
"Detail": "",
"Subject": {
"Filename": "invalid-invocation2",
"Start": {
"Line": 2,
"Column": 3,
"Byte": 10
},
"End": {
"Line": 2,
"Column": 13,
"Byte": 20
}
},
"Context": null,
"Expression": null,
"EvalContext": null,
"Extra": null,
"Path": "values[\"fn::secret\"]"
}
]
}

0 comments on commit 6e2c31a

Please sign in to comment.