Skip to content

Commit adaeb74

Browse files
committed
ensure query can be evaluated during preparation
Continuing my trend of shoving more validation into `OPA.Engine.prepareForEvaluation`, this method now checks that the provided query can be evaluated. This should help catch misconfigurations earlier in application bootstrapping, without necessarily having to evaluate something first. I think it's a bit of a deviation from how query preparation works with the Go implementation. And I could see this check becoming much more difficult or impossible to implement as query capabilities expand, so I won't be upset if this is rejected. There are already some tests checking for this behavior, but they were expecting it to happen on `evaluate`. Now the error pops up during `prepareForEvaluation`, but the tests pass either way. Signed-off-by: Chris Rice <[email protected]>
1 parent e1d3718 commit adaeb74

File tree

3 files changed

+37
-17
lines changed

3 files changed

+37
-17
lines changed

Sources/Rego/Engine.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,21 @@ extension OPA.Engine {
104104
try await store.write(to: StoreKeyPath(["data"]), value: bundle.data)
105105
}
106106

107+
let evaluator: Evaluator
107108
if self.policies.count > 0 {
108109
guard loadedBundles.isEmpty else {
109110
throw RegoError.init(code: .invalidArgumentError, message: "Cannot mix direct IR policies with bundles")
110111
}
111-
return PreparedQuery(
112-
query: query,
113-
evaluator: IREvaluator(policies: self.policies),
114-
store: self.store
115-
)
112+
evaluator = IREvaluator(policies: self.policies)
113+
} else {
114+
evaluator = try IREvaluator(bundles: loadedBundles)
116115
}
117116

117+
try evaluator.ensureQueryIsSupported(query)
118+
118119
return PreparedQuery(
119120
query: query,
120-
evaluator: try IREvaluator(bundles: loadedBundles),
121+
evaluator: evaluator,
121122
store: self.store
122123
)
123124
}

Sources/Rego/Evaluator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import AST
33
import IR
44

55
protocol Evaluator {
6+
func ensureQueryIsSupported(_ query: String) throws
67
func evaluate(withContext ctx: EvaluationContext) async throws -> ResultSet
78
}
89

Sources/Rego/IREvaluator.swift

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,42 @@ internal struct IREvaluator {
3232
init(policies: [IR.Policy]) {
3333
self.policies = policies.map { IndexedIRPolicy(policy: $0) }
3434
}
35+
36+
func findPlanAndPolicy(entrypoint: String) -> (IndexedIRPolicy, Plan)? {
37+
// TODO: We're assuming that queries are only ever defined in a single policy... that _should_ hold true.. but who's checkin?
38+
guard let policy = policies.first(where: { $0.plans[entrypoint] != nil }) else {
39+
return nil
40+
}
41+
42+
// The above guard has already ensured that the policy has a plan for the given entrypoint, so force unwrap is safe.
43+
return (policy, policy.plans[entrypoint]!)
44+
}
45+
46+
func unknownQueryError(query: String) -> RegoError {
47+
RegoError(code: .unknownQuery, message: "query not found in plan: \(query)")
48+
}
3549
}
3650

3751
extension IREvaluator: Evaluator {
38-
func evaluate(withContext ctx: EvaluationContext) async throws -> ResultSet {
39-
// TODO: We're assuming that queries are only ever defined in a single policy... that _should_ hold true.. but who's checkin?
52+
func ensureQueryIsSupported(_ query: String) throws {
53+
let entrypoint = try queryToEntryPoint(query)
4054

55+
guard findPlanAndPolicy(entrypoint: entrypoint) != nil else {
56+
throw unknownQueryError(query: query)
57+
}
58+
}
59+
60+
func evaluate(withContext ctx: EvaluationContext) async throws -> ResultSet {
4161
let entrypoint = try queryToEntryPoint(ctx.query)
4262

43-
for policy in policies {
44-
if let plan = policy.plans[entrypoint] {
45-
let ctx = IREvaluationContext(ctx: ctx, policy: policy)
46-
return try await evalPlan(
47-
withContext: ctx,
48-
plan: plan
49-
)
50-
}
63+
guard let (policy, plan) = findPlanAndPolicy(entrypoint: entrypoint) else {
64+
throw unknownQueryError(query: ctx.query)
5165
}
52-
throw RegoError(code: .unknownQuery, message: "query not found in plan: \(ctx.query)")
66+
67+
return try await evalPlan(
68+
withContext: IREvaluationContext(ctx: ctx, policy: policy),
69+
plan: plan
70+
)
5371
}
5472
}
5573

0 commit comments

Comments
 (0)