Skip to content

Commit 336407b

Browse files
DFrenkelsrenatus
authored andcommitted
builtins: product, sort, sum
Also: implementing internal.member_3 for compliance testing Resolves #12 Signed-off-by: Dmitry Frenkel <[email protected]>
1 parent 29d8199 commit 336407b

File tree

7 files changed

+330
-10
lines changed

7 files changed

+330
-10
lines changed

Sources/Rego/Builtins/Aggregates.swift

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension BuiltinFuncs {
99

1010
guard let len = args[0].count else {
1111
throw BuiltinError.argumentTypeMismatch(
12-
arg: "collection", got: args[0].typeName, want: "string|array|object|set")
12+
arg: "collection", got: args[0].typeName, want: "any<array, object, set, string>")
1313
}
1414

1515
return .number(NSNumber(value: len))
@@ -27,7 +27,7 @@ extension BuiltinFuncs {
2727
return s.max() ?? .undefined
2828
default:
2929
throw BuiltinError.argumentTypeMismatch(
30-
arg: "collection", got: args[0].typeName, want: "array|set")
30+
arg: "collection", got: args[0].typeName, want: "any<array[any], set[any]>")
3131
}
3232
}
3333

@@ -43,7 +43,74 @@ extension BuiltinFuncs {
4343
return s.min() ?? .undefined
4444
default:
4545
throw BuiltinError.argumentTypeMismatch(
46-
arg: "collection", got: args[0].typeName, want: "array|set")
46+
arg: "collection", got: args[0].typeName, want: "any<array[any], set[any]>")
47+
}
48+
}
49+
50+
static func sort(ctx: BuiltinContext, args: [AST.RegoValue]) async throws -> AST.RegoValue {
51+
guard args.count == 1 else {
52+
throw BuiltinError.argumentCountMismatch(got: args.count, want: 1)
53+
}
54+
55+
switch args[0] {
56+
case .array(let a):
57+
return .array(a.sorted())
58+
case .set(let s):
59+
return .array(s.sorted())
60+
default:
61+
throw BuiltinError.argumentTypeMismatch(
62+
arg: "collection", got: args[0].typeName, want: "any<array[any], set[any]>")
63+
}
64+
}
65+
66+
static func sum(ctx: BuiltinContext, args: [AST.RegoValue]) async throws -> AST.RegoValue {
67+
return try doReduce(args: args, initialValue: .number(0), op: BuiltinFuncs.doPlus)
68+
}
69+
70+
static func product(ctx: BuiltinContext, args: [AST.RegoValue]) async throws -> AST.RegoValue {
71+
return try doReduce(args: args, initialValue: .number(1), op: BuiltinFuncs.doMul)
72+
}
73+
74+
/// Returns reduction over an array or set of RegoValues with a given async Builtin being used as an reducer operation.
75+
/// Returns the normalized metric unit symbol for a given symbol.
76+
/// - Parameters:
77+
/// - ctx: The builtin context.
78+
/// - args: The arguments to reduce.
79+
/// - initialValue: The initial value to start with.
80+
/// - op: The Arithmetic builtin operation to be applied to the partial result an the next value in the sequence to produce the next result.
81+
private static func doReduce(
82+
args: [AST.RegoValue],
83+
initialValue: AST.RegoValue,
84+
op: ([AST.RegoValue]) throws -> AST.RegoValue
85+
) throws -> AST.RegoValue {
86+
guard args.count == 1 else {
87+
throw BuiltinError.argumentCountMismatch(got: args.count, want: 1)
88+
}
89+
90+
// We will iterate over this sequence
91+
var sequence: any Sequence<RegoValue>
92+
switch args[0] {
93+
case .array(let a):
94+
sequence = a
95+
case .set(let s):
96+
sequence = s
97+
default:
98+
throw BuiltinError.argumentTypeMismatch(
99+
arg: "collection", got: args[0].typeName, want: "any<array[number], set[number]>")
100+
}
101+
102+
do {
103+
let result = try sequence.reduce(
104+
initialValue,
105+
{ x, y in
106+
try op([x, y])
107+
})
108+
return result
109+
} catch is RegoError {
110+
let receivedTypes = Set(sequence.map({ $0.typeName })).sorted().joined(separator: ", ")
111+
throw BuiltinError.argumentTypeMismatch(
112+
arg: "collection", got: "\(args[0].typeName)[any<\(receivedTypes)>]",
113+
want: "any<array[number], set[number]>")
47114
}
48115
}
49116
}

Sources/Rego/Builtins/Arithmetic.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ let _floor = floor
99

1010
extension BuiltinFuncs {
1111
static func plus(ctx: BuiltinContext, args: [AST.RegoValue]) async throws -> AST.RegoValue {
12+
return try doPlus(args: args)
13+
}
14+
15+
static func doPlus(args: [AST.RegoValue]) throws -> AST.RegoValue {
1216
guard args.count == 2 else {
1317
throw BuiltinError.argumentCountMismatch(got: args.count, want: 2)
1418
}
@@ -48,6 +52,10 @@ extension BuiltinFuncs {
4852
// Multiplies two numbers.
4953
// Returns: the product of `x` and `y`
5054
static func mul(ctx: BuiltinContext, args: [AST.RegoValue]) async throws -> AST.RegoValue {
55+
return try doMul(args: args)
56+
}
57+
58+
static func doMul(args: [AST.RegoValue]) throws -> AST.RegoValue {
5159
guard args.count == 2 else {
5260
throw BuiltinError.argumentCountMismatch(got: args.count, want: 2)
5361
}

Sources/Rego/Builtins/Casts.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ extension BuiltinFuncs {
3434
return .number(NSNumber(value: x))
3535
default:
3636
throw BuiltinError.argumentTypeMismatch(
37-
arg: "x", got: args[0].typeName, want: "boolean|null|number|string")
37+
arg: "x", got: args[0].typeName, want: "any<boolean, null, number, string>")
3838
}
3939
}
4040

Sources/Rego/Builtins/Collections.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,47 @@ extension BuiltinFuncs {
2121
return .boolean(false)
2222
}
2323
}
24+
25+
// internal.member_3
26+
// memberOfWithKey is a membership check with key:
27+
// memberOfWithKey(k: any, x: any, y: any) checks if y has property or index k and it is equal to x
28+
// For objects, we are checking the keys AND the values.
29+
static func isMemberOfWithKey(ctx: BuiltinContext, args: [AST.RegoValue]) async throws -> AST.RegoValue {
30+
guard args.count == 3 else {
31+
throw BuiltinError.argumentCountMismatch(got: args.count, want: 3)
32+
}
33+
34+
let key = args[0]
35+
let value = args[1]
36+
// See https://github.com/open-policy-agent/opa/blob/b942136a4ad049262fd72026421dac6bdd705059/v1/topdown/aggregates.go#L247
37+
let match = args[2][key]
38+
if match != nil {
39+
return .boolean(RegoValue.compare(value, match!) == .orderedSame)
40+
}
41+
return .boolean(false)
42+
}
43+
}
44+
45+
extension RegoValue {
46+
/// Some RegoValues implement Get(key) interface. We will just implement it here as a subscript extension.
47+
/// See https://github.com/open-policy-agent/opa/blob/7ddaff2cc3dd749af25bab7d6a1f5a9cdbfe9833/v1/ast/term.go#L380
48+
fileprivate subscript(key: RegoValue) -> RegoValue? {
49+
switch self {
50+
case .object(let o):
51+
return o[key]
52+
case .array(let a):
53+
// key must be an integer position
54+
guard !key.isFloat, let index = key.integerValue else { return nil }
55+
// check bounds
56+
guard index >= 0 && index < a.count && index < Int32.max else { return nil }
57+
return a[Int(index)]
58+
case .set(let s):
59+
if s.contains(key) {
60+
return key
61+
}
62+
return nil
63+
default:
64+
return nil
65+
}
66+
}
2467
}

Sources/Rego/Builtins/Registry.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public struct BuiltinRegistry: Sendable {
3838
"count": BuiltinFuncs.count,
3939
"max": BuiltinFuncs.max,
4040
"min": BuiltinFuncs.min,
41+
"product": BuiltinFuncs.product,
42+
"sort": BuiltinFuncs.sort,
43+
"sum": BuiltinFuncs.sum,
4144

4245
// Arithmetic
4346
"plus": BuiltinFuncs.plus,
@@ -65,6 +68,7 @@ public struct BuiltinRegistry: Sendable {
6568

6669
// Collections
6770
"internal.member_2": BuiltinFuncs.isMemberOf,
71+
"internal.member_3": BuiltinFuncs.isMemberOfWithKey,
6872

6973
// Comparison
7074
"gt": BuiltinFuncs.greaterThan,

0 commit comments

Comments
 (0)