coupergateway / couper

Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects
https://couper.io
MIT License
85 stars 15 forks source link

Add HCL function `try()`, remove nilval handling #702

Open johakoch opened 1 year ago

johakoch commented 1 year ago

For Couper 2?

Couper's internal eval error by null value replacement (in eval.value.go) is quite complicated, and still does not work in several use cases. E.g. (in case there is no request.json_body):

to_upper(request.json_body.a)

Invalid function argument; Invalid value for "str" parameter: string required.


request.json_body.a + 2

Invalid operand; Unsuitable value for left operand: number required.


request.json_body.a < 2

Invalid operand; Unsuitable value for left operand: number required.


request.json_body.a ? "ok" : "nok"

Unsupported attribute; This object does not have an attribute named "a".


!request.json_body.a ? "ok" : "nok"

Invalid operand; Unsuitable value for unary operand: bool required.


[ for v in request.json_body.a : v ]

Iteration over null value; A null value cannot be used as the collection in a 'for' expression.


[ for k in ["a", "b"] : request.json_body[k] ]

Invalid index; The given key does not identify an element in this collection value., and 1 other diagnostic(s)


{a = 1, b = 2}[request.json_body.a]

Invalid index; Can't use a null value as an indexing key.


I would rather provide the HCL try() function and expect config writers to use it and set an appropriate default value (may be null in some cases, can't be null in others) themselves. They will know better than the developers of Couper.

See documentation of try() and can() (https://pkg.go.dev/github.com/hashicorp/hcl/v2/ext/tryfunc#section-readme):

Both of these are primarily intended for working with deep data structures which might not have a dependable shape. For example, we can use try to attempt to fetch a value from deep inside a data structure but produce a default value if any step of the traversal fails:

result = try(foo.deep[0].lots.of["traversals"], null)

The final result to try should generally be some sort of constant value that will always evaluate successfully.

johakoch commented 1 year ago

From the examples above, only one could currently be "healed" with the (to be provided) try function:

[ for k in ["a", "b"] : try(request.json_body[k], null ) ] # returns [null,null]

The other examples would still throw an error, because request.json_body.a is replaced with null.