Skip to content

Commit

Permalink
Support JSON double values being reencoded.
Browse files Browse the repository at this point in the history
When the JSON value 2 is decoded, it is a Double, so arithmetic on it returns doubles.

It can be cast into an integer value which make it slightly easier to
read.

With further illustrative tests and documentation.
  • Loading branch information
bigkevmcd committed Mar 6, 2020
1 parent 820d8b3 commit bd4c958
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 9 deletions.
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

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
11 changes: 11 additions & 0 deletions pkg/interceptors/cel/cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ func (w *Interceptor) ExecuteTrigger(request *http.Request) (*http.Response, err
if err == nil {
b, err = json.Marshal(raw.(*structpb.Value).GetStringValue())
}
case types.Double:
raw, err = val.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
if err == nil {
b, err = json.Marshal(raw.(*structpb.Value).GetNumberValue())
}
case 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

0 comments on commit bd4c958

Please sign in to comment.