Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JSON double values being reencoded in the CEL interceptor #470

Merged
merged 1 commit into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions docs/cel_expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,67 @@ In addition to the custom function extension listed below, you can craft any
valid CEL expression as defined by the
[cel-spec language definition](https://github.com/google/cel-spec/blob/master/doc/langdef.md)

### List of extensions

## Notes on numbers in CEL expressions

One thing to be aware of is how numeric values are treated in CEL expressions, JSON numbers are decoded to [CEL double](https://github.com/google/cel-spec/blob/master/doc/langdef.md#values) values.

For example:

```json
{
"count": 2,
"measure": 1.7
}
```

In the JSON above, both numbers are parsed as floating point values.

This means that if you want to do integer arithmetic, you'll need to [use explicit conversion functions](https://github.com/google/cel-spec/blob/master/doc/langdef.md#numeric-values).

From the CEL specification:

> Note that currently there are no automatic arithmetic conversions for the numeric types (int, uint, and double).

You can either explicitly convert the number, or add another double value e.g.

```yaml
interceptors:
- cel:
overlays:
- key: count_plus_1
expression: "body.count + 1.0"
- key: count_plus_2
expression: "int(body.count) + 2"
- key: measure_times_3
expression: "body.measure * 3.0"
```

These will be serialised back to JSON appropriately:

```json
{
"count_plus_1": 2,
"count_plus_2": 3,
"measure_times_3": 5.1
}
```

### Error messages in conversions

The following example will generate an error with the JSON example.

```yaml
interceptors:
- cel:
overlays:
- key: bad_measure_times_3
expression: "body.measure * 3"
```

**bad_measure_times_3** will fail with `failed to evaluate overlay expression 'body.measure * 3': no such overload` because there's no automatic conversion.

## List of extensions

The body from the `http.Request` value is decoded to JSON and exposed, and the
headers are also available.
Expand Down Expand Up @@ -59,7 +119,7 @@ type Header map[string][]string
i.e. the header is a mapping of strings, to arrays of strings, see the `match`
function on headers below for an extension that makes looking up headers easier.

### List of extension functions
## List of extension functions

This lists custom functions that can be used from CEL expressions in the CEL
interceptor.
Expand Down
6 changes: 6 additions & 0 deletions pkg/interceptors/cel/cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ func (w *Interceptor) ExecuteTrigger(request *http.Request) (*http.Response, err
if err == nil {
b, err = json.Marshal(raw.(*structpb.Value).GetStringValue())
}
case types.Double, types.Int:
raw, err = val.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
if err == nil {
b, err = json.Marshal(raw.(*structpb.Value).GetNumberValue())
}

default:
raw, err = val.ConvertToNative(reflect.TypeOf([]byte{}))
b = raw.([]byte)
Expand Down
33 changes: 26 additions & 7 deletions pkg/interceptors/cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ func TestInterceptor_ExecuteTrigger(t *testing.T) {
payload: nil,
want: []byte(`{}`),
},
{
name: "incrementing an integer value",
CEL: &triggersv1.CELInterceptor{
Overlays: []triggersv1.CELOverlay{
{Key: "val1", Expression: "body.count + 1.0"},
{Key: "val2", Expression: "int(body.count) + 3"},
{Key: "val3", Expression: "body.count + 3.5"},
{Key: "val4", Expression: "body.measure * 3.0"},
},
},
payload: ioutil.NopCloser(bytes.NewBufferString(`{"count":1,"measure":1.7}`)),
want: []byte(`{"val4":5.1,"val3":4.5,"val2":4,"val1":2,"count":1,"measure":1.7}`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -243,9 +256,12 @@ func TestExpressionEvaluation(t *testing.T) {
testSHA := "ec26c3e57ca3a959ca5aad62de7213c562f8c821"
testRef := "refs/heads/master"
jsonMap := map[string]interface{}{
"value": "testing",
"sha": testSHA,
"ref": testRef,
"value": "testing",
"sha": testSHA,
"ref": testRef,
"pull_request": map[string]interface{}{
"commits": 2,
},
"b64value": "ZXhhbXBsZQ==",
}
refParts := strings.Split(testRef, "/")
Expand Down Expand Up @@ -307,9 +323,9 @@ func TestExpressionEvaluation(t *testing.T) {
want: types.Bytes("example"),
},
{
name: "decode a base64 value",
expr: "decodeb64(body.b64value)",
want: types.Bytes("example"),
name: "increment an integer",
expr: "body.pull_request.commits + 1",
want: types.Int(3),
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -337,9 +353,12 @@ func TestExpressionEvaluation_Error(t *testing.T) {
jsonMap := map[string]interface{}{
"value": "testing",
"sha": testSHA,
"pull_request": map[string]interface{}{
"commits": []string{},
},
}
header := http.Header{}
evalEnv := map[string]interface{}{"body": jsonMap, "headers": header}
evalEnv := map[string]interface{}{"body": jsonMap, "header": header}
env, err := makeCelEnv()
if err != nil {
t.Fatal(err)
Expand Down