Union an array of objects in rego - open-policy-agent

How do I union an array of objects into one object in rego?
I know how to union two objects, but how do I loop through an array of objects?
This sample works, but it is (obvious) not scalable ;)
a := [{"test1": "123"}, {"test2": "456"}, {"test3": "789", "test4": "012"}]
b := {
"my-property": object.union(object.union(a[0], a[1]), a[2])
}
Expected output:
{
"test1": "123",
"test2": "456",
"test3": "789",
"test4": "012"
}
Thanks!
Casper

TLDR; assuming the object keys are unique (like in your example), you can simply use a comprehension:
b := {k: v |
some i, k
v := a[i][k]
}
Since the variable i is only used once, you can replace it with _ to avoid having to come up with a name:
b := {k: v |
some k
v := a[_][k]
}
If the object keys are not unique then it's slightly more complicated because you need to decide how to resolve conflicts when two objects contain the same key with different values. How you decide to resolve conflicts will depend on the use case. One way to resolve them is to just group values by their keys. For example:
# the 'test2' key appears in more than one object with a different value
a := [{"test1": "123"}, {"test2": "456"}, {"test3": "789", "test2": "012"}]
To group the values by keys, you can write a nested comprehension:
{k: vs |
# for each key 'k'
some k
conflicting[_][k]
# find all values for key 'k' and group as a set
vs := {v | v := conflicting[_][k]}
}
Output:
{
"test1": [
"123"
],
"test2": [
"012",
"456"
],
"test3": [
"789"
]
}

Related

How do I sort a simple Lua table alphabetically?

I have already seen many threads with examples of how to do this, the problem is, I still can't do it.
All the examples have tables with extra data. For example somethings like this
lines = {
luaH_set = 10,
luaH_get = 24,
luaH_present = 48,
}
or this,
obj = {
{ N = 'Green1' },
{ N = 'Green' },
{ N = 'Sky blue99' }
}
I can code in a few languages but I'm very new to Lua, and tables are really confusing to me. I can't seem to work out how to adapt the code in the examples to be able to sort a simple table.
This is my table:
local players = {"barry", "susan", "john", "wendy", "kevin"}
I want to sort these names alphabetically. I understand that Lua tables don't preserve order, and that's what's confusing me. All I essentially care about doing is just printing these names in alphabetical order, but I feel I need to learn this properly and know how to index them in the right order to a new table.
The examples I see are like this:
local function cmp(a, b)
a = tostring(a.N)
b = tostring(b.N)
local patt = '^(.-)%s*(%d+)$'
local _,_, col1, num1 = a:find(patt)
local _,_, col2, num2 = b:find(patt)
if (col1 and col2) and col1 == col2 then
return tonumber(num1) < tonumber(num2)
end
return a < b
end
table.sort(obj, cmp)
for i,v in ipairs(obj) do
print(i, v.N)
end
or this:
function pairsByKeys (t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
for name, line in pairsByKeys(lines) do
print(name, line)
end
and I'm just absolutely thrown by this as to how to do the same thing for a simple 1D table.
Can anyone please help me to understand this? I know if I can understand the most basic example, I'll be able to teach myself these harder examples.
local players = {"barry", "susan", "john", "wendy", "kevin"}
-- sort ascending, which is the default
table.sort(players)
print(table.concat(players, ", "))
-- sort descending
table.sort(players, function(a,b) return a > b end)
print(table.concat(players, ", "))
Here's why:
Your table players is a sequence.
local players = {"barry", "susan", "john", "wendy", "kevin"}
Is equivalent to
local players = {
[1] = "barry",
[2] = "susan",
[3] = "john",
[4] = "wendy",
[5] = "kevin",
}
If you do not provide keys in the table constructor, Lua will use integer keys automatically.
A table like that can be sorted by its values. Lua will simply rearrange the index value pairs in respect to the return value of the compare function. By default this is
function (a,b) return a < b end
If you want any other order you need to provide a function that returs true if element a comes befor b
Read this https://www.lua.org/manual/5.4/manual.html#pdf-table.sort
table.sort
Sorts the list elements in a given order, in-place, from list[1] to
list[#list]
This example is not a "list" or sequence:
lines = {
luaH_set = 10,
luaH_get = 24,
luaH_present = 48,
}
Which is equivalent to
lines = {
["luaH_set"] = 10,
["luaH_get"] = 24,
["luaH_present"] = 48,
}
it only has strings as keys. It has no order. You need a helper sequence to map some order to that table's element.
The second example
obj = {
{ N = 'Green1' },
{ N = 'Green' },
{ N = 'Sky blue99' }
}
which is equivalent to
obj = {
[1] = { N = 'Green1' },
[2] = { N = 'Green' },
[3] = { N = 'Sky blue99' },
}
Is a list. So you could sort it. But sorting it by table values wouldn't make too much sense. So you need to provide a function that gives you a reasonable way to order it.
Read this so you understand what a "sequence" or "list" is in this regard. Those names are used for other things as well. Don't let it confuse you.
https://www.lua.org/manual/5.4/manual.html#3.4.7
It is basically a table that has consecutive integer keys starting at 1.
Understanding this difference is one of the most important concepts while learning Lua. The length operator, ipairs and many functions of the table library only work with sequences.
This is my table:
local players = {"barry", "susan", "john", "wendy", "kevin"}
I want to sort these names alphabetically.
All you need is table.sort(players)
I understand that LUA tables don't preserve order.
Order of fields in a Lua table (a dictionary with arbitrary keys) is not preserved.
But your Lua table is an array, it is self-ordered by its integer keys 1, 2, 3,....
To clear up the confusing in regards to "not preserving order": What's not preserving order are the keys of the values in the table, in particular for string keys, i.e. when you use the table as dictionary and not as array. If you write myTable = {orange="hello", apple="world"} then the fact that you defined key orange to the left of key apple isn't stored. If you enumerate keys/values using for k, v in pairs(myTable) do print(k, v) end then you'd actually get apple world before orange hello because "apple" < "orange".
You don't have this problem with numeric keys though (which is what the keys by default will be if you don't specify them - myTable = {"hello", "world", foo="bar"} is the same as myTable = {[1]="hello", [2]="world", foo="bar"}, i.e. it will assign myTable[1] = "hello", myTable[2] = "world" and myTable.foo = "bar" (same as myTable["foo"]). (Here, even if you would get the numeric keys in a random order - which you don't, it wouldn't matter since you could still loop through them by incrementing.)
You can use table.sort which, if no order function is given, will sort the values using < so in case of numbers the result is ascending numbers and in case of strings it will sort by ASCII code:
local players = {"barry", "susan", "john", "wendy", "kevin"}
table.sort(players)
-- players is now {"barry", "john", "kevin", "susan", "wendy"}
This will however fall apart if you have mixed lowercase and uppercase entries because uppercase will go before lowercase due to having lower ASCII codes, and of course it also won't work properly with non-ASCII characters like umlauts (they will go last) - it's not a lexicographic sort.
You can however supply your own ordering function which receives arguments (a, b) and needs to return true if a should come before b. Here an example that fixes the lower-/uppercase issues for example, by converting to uppercase before comparing:
table.sort(players, function (a, b)
return string.upper(a) < string.upper(b)
end)

Rego rule to check for employee name in a list

I am new to rego code and writing a rule to check for employee names if they present in the approved employee list. If not, it should print out the employees who are not part of the list. Here is the input I am giving:
{
"valid_employee_names": {
"first_name.2113690404": "emp_Maria",
"first_name.2641279496": "emp_Rosie",
"first_name.3921413181": "emp_Shaun",
"first_name.588579514": "emp_John"
},
"destroy": false
}
Here is the rego rule:
valid_name := {i: Reason |
check_list := {["emp_John","emp_Monika","emp_Cindy","emp_Katie","emp_Kein"]}
doc = input[i]; i="valid_employee_names"
key_ids := [k | doc[k]; startswith(k, "first_name.")]
resource := {
doc[k] : doc[replace(k, ".key", ".value")] | key_ids[_] == k
}
emp_name := [m | resource[m]; startswith(m, "emp_")]
list := {x| x:=check_list[_]}
not(list[emp_name])
Reason := sprintf("Employees not on record found - %v . :: ", emp_name)
}
I always get the same output that employees not on record are found.
Can anyone point me to see what needs to be corrected/updated?
Thanks!
The rule you posted is quite complicated. If you only need to compute the set of employee names NOT in the approved list (which I assume is input.valid_employee_names) start by computing a set of valid names and then take the difference.
# valid_employee_names generates a set of valid employee (first) names from the input.
valid_employee_names := {v |
some k
v := input.valid_employee_names[k]
startswith(k, "first_name.")}
I'm assuming the list of names to check against is check_list. Note, you have check_list defined as a set containing an array of names (represented as strings). There is no obvious need for the extra array. Just define the check_list as a set of names:
check_list := {"emp_John", "emp_Monika", "emp_Cindy", "emp_Katie", "emp_Kein"}
Now we have two sets we can easily find the invalid names using set difference:
check_list - valid_employee_names
Putting it all together:
valid_employee_names := {v |
some k
v := input.valid_employee_names[k]
startswith(k, "first_name.")}
check_list := {"emp_John", "emp_Monika", "emp_Cindy", "emp_Katie", "emp_Kein"}
invalid_names := check_list - valid_employee_names
(playground link: https://play.openpolicyagent.org/p/5KfXnSIkHa)

OR in Open Policy Agent (union behavior)

In OPA it's clear how to query against condition AND condition:
values := {
"value1": {
"a": "one"
},
"value2": {
"a": "one",
"b": "two"
},
"value3": {
"a": "one",
"b": "one"
}
}
goodValues = [name |
value = values[name]
value.a == "one"
value.b == "one"
]
So that goodValues here will contain value3 only.
But how to query condition OR condition, so that goodValues will contain all 3 values, because they have either value.a == "one" OR value.b == "one"?
Joining multiple expressions together expresses logical AND. To express logical OR you define multiple rules or functions with the same name. There are a couple different ways this can work. This is covered in the introduction to OPA: https://www.openpolicyagent.org/docs/latest/#logical-or.
Option 1: Comprehensions & Functions
The conditions that you want to express against the value can be factored into helper functions and then the comprehension query can refer to the function.
goodValues = [name |
value := values[name]
value_match(value)
]
value_match(v) {
v.a == "one"
}
value_match(v) {
v.b = "two"
}
Option 2: Incremental Rules
In OPA/Rego, incremental rules assign a set of values to a variable. The rule definition provides the logic to generate the set values. Unlike comprehensions, you can overload the rule definition (providing multiple with the same name) and express logical OR like the other answer explains.
# goodValues is a set that contains 'name' if...
goodValues[name] {
value := values[name] # name is in values
value.a == "one" # value.a is "one"
}
# goodvalues is a set that contains 'name' if...
goodValues[name] {
value := values[name] # name is in values
value.b == "two" # value.b is "two"
}
Found an ugly answer so far, via incremental set:
goodValues[name] {
value = values[name]
value.a == "one"
}
goodValues[name] {
value = values[name]
value.b == "one"
}
But what if that common condition value = values[name] gets more complicated? Will need to extract it to a separate variable (and iterate over that in every condition statement)? Any better solution?

Neo4J - Return everything from node but property key

I have generalized cypher that returns different types of nodes whose property names may vary from which I need to exclude any property name called "password". I've tried using a CASE statement inside of EXTRACT, FILTER and REDUCE but I am not getting anywhere.
RETURN reduce(props = {}, x IN keys(stateNode) |
case when x <> "password" then
props + {x: stateNode[x]}
else
props + {"password": "changed"}
end
)
This obviously won't work but I am not sure how else to do it. I don't have the luxury of knowing what the other property names are so I cant explicitly return every property excluding "password" property.
If it is OK to return an array of arrays (where each inner array contains a key/value pair), then you can use something like this:
RETURN REDUCE(s = [], x IN keys(stateNode) |
s + [[x, (CASE WHEN x = "password" THEN "changed" ELSE stateNode[x] END)]]
) AS props;
The result would look something like:
[["name","Fred"],["age",22],["password","changed"]]

Using Neo4j Cypher to find which nodes are not related to each other

In Neo4j, I create a small graph with 4 nodes, some of which are linked to some others:
CREATE
(a:Room {name:"A"})
-[:DOOR]->
(b:Room {name:"B"})
-[:DOOR]->
(c:Room {name:"C"})
-[:DOOR]->
(d:Room {name:"D"}),
a-[:DOOR]->c,
a-[:DOOR]->d,
b-[:DOOR]->a
RETURN a,b,c,d
I want to find which rooms do not have a door between them. I'm hoping for an output something like this:
{"B": ["D"], "C": ["A", "B"], "D": ["A", "B", "C"]}
I can do this for one given starting point...
MATCH (b), (r)
WHERE b.name = "B"
AND NOT (b)-[:DOOR]->(r)
AND b <> r
RETURN r
// Returns Room D
Here's my cargo-cult pseudo code for iterating through each possible pair of nodes:
MATCH rooms = (r)
SET output = {}
FOREACH (
room IN nodes(rooms),
exit IN nodes(rooms),
missing = [],
output[room.name] = missing
|
IF room <> exit AND NOT room-[:DOOR]->(exit)
THEN missing = missing + exit
)
RETURN output
Please help me to understand how to formulate this correctly in Cypher.
The WHERE clause takes relationship patterns and you can use the NOT function to filter on the absence of a relationship.
MATCH (a:Room), (b:Room)
WHERE NOT a-[:DOOR]-b AND a <> b
RETURN a, b
Here's the section in the docs.

Resources