Skip to content

Commit

Permalink
Added support for dereferenced map properties (#2635)
Browse files Browse the repository at this point in the history
* Added support for dereferenced properties

* Added negative test

* Update pkg/exprparser/functions_test.go

Co-authored-by: ChristopherHX <[email protected]>

* Update pkg/exprparser/functions_test.go

Co-authored-by: ChristopherHX <[email protected]>

* fix lint

---------

Co-authored-by: m1r4c <[email protected]>
Co-authored-by: ChristopherHX <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2025
1 parent 7c45ad6 commit 9dd0854
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 0 deletions.
27 changes: 27 additions & 0 deletions pkg/exprparser/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ func TestFunctionContains(t *testing.T) {
{`contains(fromJSON('[3.14,"second"]'), 3.14) }}`, true, "contains-item-number-number"},
{`contains(fromJSON('["","second"]'), fromJSON('[]')) }}`, false, "contains-item-str-arr"},
{`contains(fromJSON('["","second"]'), fromJSON('{}')) }}`, false, "contains-item-str-obj"},
{`contains(fromJSON('[{ "first": { "result": "success" }},{ "second": { "result": "success" }}]').first.result, 'success') }}`, true, "multiple-contains-item"},
{`contains(fromJSON('[{ "result": "success" },{ "result": "failure" }]').*.result, 'failure') }}`, true, "multiple-contains-dereferenced-failure-item"},
{`contains(fromJSON('[{ "result": "failure" },{ "result": "success" }]').*.result, 'success') }}`, true, "multiple-contains-dereferenced-success-item"},
{`contains(fromJSON('[{ "result": "failure" },{ "result": "success" }]').*.result, 'notthere') }}`, false, "multiple-contains-dereferenced-missing-item"},
{`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'val1') }}`, true, "multiple-contains-dereferenced-output-item"},
{`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'val2') }}`, true, "multiple-contains-dereferenced-output-item-2"},
{`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'missing') }}`, false, "multiple-contains-dereferenced-output-misssing-item"},
}

env := &EvaluationEnvironment{}
Expand Down Expand Up @@ -249,3 +256,23 @@ func TestFunctionFormat(t *testing.T) {
})
}
}

func TestMapContains(t *testing.T) {
env := &EvaluationEnvironment{
Needs: map[string]Needs{
"first-job": {
Outputs: map[string]string{},
Result: "success",
},
"second-job": {
Outputs: map[string]string{},
Result: "failure",
},
},
}

output, err := NewInterpeter(env, Config{}).Evaluate("contains(needs.*.result, 'failure')", DefaultStatusCheckNone)
assert.Nil(t, err)

assert.Equal(t, true, output)
}
32 changes: 32 additions & 0 deletions pkg/exprparser/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.Obj
return nil, err
}

_, receiverIsDeref := objectDerefNode.Receiver.(*actionlint.ArrayDerefNode)
if receiverIsDeref {
return impl.getPropertyValueDereferenced(reflect.ValueOf(left), objectDerefNode.Property)
}
return impl.getPropertyValue(reflect.ValueOf(left), objectDerefNode.Property)
}

Expand Down Expand Up @@ -312,6 +316,34 @@ func (impl *interperterImpl) getPropertyValue(left reflect.Value, property strin
return nil, nil
}

func (impl *interperterImpl) getPropertyValueDereferenced(left reflect.Value, property string) (value interface{}, err error) {
switch left.Kind() {
case reflect.Ptr:
return impl.getPropertyValue(left, property)

case reflect.Struct:
return impl.getPropertyValue(left, property)
case reflect.Map:
iter := left.MapRange()

var values []interface{}
for iter.Next() {
value, err := impl.getPropertyValue(iter.Value(), property)
if err != nil {
return nil, err
}

values = append(values, value)
}

return values, nil
case reflect.Slice:
return impl.getPropertyValue(left, property)
}

return nil, nil
}

func (impl *interperterImpl) getMapValue(value reflect.Value) (interface{}, error) {
if value.Kind() == reflect.Ptr {
return impl.getMapValue(value.Elem())
Expand Down
8 changes: 8 additions & 0 deletions pkg/exprparser/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,8 @@ func TestContexts(t *testing.T) {
{"matrix.os", "Linux", "matrix-context"},
{"needs.job-id.outputs.output-name", "value", "needs-context"},
{"needs.job-id.result", "success", "needs-context"},
{"contains(needs.*.result, 'success')", true, "needs-wildcard-context-contains-success"},
{"contains(needs.*.result, 'failure')", false, "needs-wildcard-context-contains-failure"},
{"inputs.name", "value", "inputs-context"},
}

Expand Down Expand Up @@ -610,6 +612,12 @@ func TestContexts(t *testing.T) {
},
Result: "success",
},
"another-job-id": {
Outputs: map[string]string{
"output-name": "value",
},
Result: "success",
},
},
Inputs: map[string]interface{}{
"name": "value",
Expand Down

0 comments on commit 9dd0854

Please sign in to comment.