opa icon indicating copy to clipboard operation
opa copied to clipboard

Incremental definition of function output

Open matt-phylum opened this issue 2 years ago • 4 comments

What is the underlying problem you're trying to solve?

I have an OPA policy that applies a per-item policy to a large collection of input.

What I would like to do is something like this

item(i) contains "a is 1" if {
    i.a == 1
}

item(i) contains "b is 1" if {
    i.b == 1
}

However, this is not allowed. It's possible to write the similar rules

item(i) := ["a is 1"] if {
    i.a == 1
}

item(i) := ["b is 1"] if {
    i.b == 1
}

but these rules are not the same. If both a and b are 1, instead of yielding ["a is 1", "b is 1"], an eval_conflict_error occurs.

Describe the ideal solution

Functions should be able to contribute to a set the same as regular rules.

Describe a "Good Enough" solution

I found a workaround:

item_workaround contains ["a is 1"] if {
    input.item.a == 1
}

item_workaround contains ["b is 1"] if {
    input.item.b == 1
}

This can be called using item_workaround with input.item as …. However, there is a performance problem (#5802) when there is a large number of items. If there's some other way to accomplish it that would also be fine.

Additional Context

# example.rego
package example
import future.keywords

item(i) := ["a is 1"] if {
    i.a == 1
}

item(i) := ["b is 1"] if {
    i.b == 1
}

item_workaround contains ["a is 1"] if {
    input.item.a == 1
}

item_workaround contains ["b is 1"] if {
    input.item.b == 1
}
# example_test.rego
package example
import future.keywords

test_item_a {
    item({"a": 1, "b": 0})
}
test_item_b {
    item({"a": 0, "b": 1})
}
test_item_both {
    # errors
    item({"a": 1, "b": 1})
}
test_item_neither {
    not item({"a": 0, "b": 0})
}

test_item_workaround_a {
    item_workaround with input.item as {"a": 1, "b": 0}
}
test_item_workaround_b {
    item_workaround with input.item as {"a": 0, "b": 1}
}
test_item_workaround_both {
    item_workaround with input.item as {"a": 1, "b": 1}
}
test_item_workaround_neither {
    result = item_workaround with input.item as {"a": 0, "b": 0}
    count(result) == 0
}

matt-phylum avatar Mar 30 '23 18:03 matt-phylum

Agreed, I would also find this useful in some cases.

anderseknert avatar Apr 03 '23 09:04 anderseknert

This issue has been automatically marked as inactive because it has not had any activity in the last 30 days.

stale[bot] avatar May 03 '23 11:05 stale[bot]

I think in current OPA it's usually possible to get close enough.

All the parameters to item() need to be enumerable. Then the rules can be written as:

item[i] contains "a is 1" if {
    some i in input.items
    item.a == 1
}

item[i] contains "b is 1" if {
    some i in input.items
    item.b == 1
}

using a list rule that materializes the results of evaluating the function as indexable data rather than the result of a function call.

This doesn't use with so it shouldn't be affected by the same performance problem as the previous workaround.

matt-phylum avatar Jan 05 '24 20:01 matt-phylum

This issue has been automatically marked as inactive because it has not had any activity in the last 30 days. Although currently inactive, the issue could still be considered and actively worked on in the future. More details about the use-case this issue attempts to address, the value provided by completing it or possible solutions to resolve it would help to prioritize the issue.

stale[bot] avatar Feb 04 '24 21:02 stale[bot]