Mainly making transactions cheaper to create, and read transactions much cheaper. - Add exported RootPath shorthand var - Don't return path on ParsePathEscaped failure - Allocate nothing for read transactions, other than the transaction itself - Lazy init of write update collections to avoid needless allocations - Add benchmarks **Before** ``` BenchmarkNewTransaction/Read-16 26707234 44.78 ns/op 144 B/op 3 allocs/op BenchmarkNewTransaction/Write-16 20344212 59.44 ns/op 192 B/op 4 allocs/op BenchmarkReadOne/Go_store_(roundtrip)-16 21963003 54.41 ns/op 144 B/op 3 allocs/op BenchmarkReadOne/Go_store_(no_roundtrip)-16 22217593 54.18 ns/op 144 B/op 3 allocs/op BenchmarkReadOne/AST_store_(roundtrip)-16 15626653 76.52 ns/op 160 B/op 4 allocs/op BenchmarkReadOne/AST_store_(no_roundtrip)-16 15820837 76.15 ns/op 160 B/op 4 allocs/op ``` **After** ``` BenchmarkNewTransaction/Read-16 68091271 17.37 ns/op 48 B/op 1 allocs/op BenchmarkNewTransaction/Write-16 24928028 47.68 ns/op 144 B/op 3 allocs/op BenchmarkReadOne/Go_store_(roundtrip)-16 42967630 28.10 ns/op 48 B/op 1 allocs/op BenchmarkReadOne/Go_store_(no_roundtrip)-16 43825009 27.63 ns/op 48 B/op 1 allocs/op BenchmarkReadOne/AST_store_(roundtrip)-16 24885938 48.06 ns/op 64 B/op 2 allocs/op BenchmarkReadOne/AST_store_(no_roundtrip)-16 25012396 47.96 ns/op 64 B/op 2 allocs/op ``` Signed-off-by: Anders Eknert <anders@eknert.com>
OPA Debug API
This directory contains the OPA Debug API. The Debug API facilitates programmatic debugging of Rego policies, on top of which 3rd parties can build tools for debugging.
This API takes inspiration from the Debug Adapter Protocol (DAP), and follows the conventions established therein for managing threads, breakpoints, and variable scopes.
Tip
The Debug API current actively supported in two clients VS Code and Neovim. Both Regal's Debug Adapter as the backend, which is based on this API.
Warning
The Debug API is experimental and subject to change.
Creating a Debug Session
debugger := debug.NewDebugger()
ctx := context.Background()
evalProps := debug.EvalProperties{
Query: "data.example.allow = x",
InputPath: "/path/to/input.json",
LaunchProperties: LaunchProperties{
DataPaths: []string{"/path/to/data.json", "/path/to/policy.rego"},
},
}
session, err := s.debugger.LaunchEval(ctx, evalProps)
if err != nil {
// handle error
}
// The session is launched in a paused state.
// Before resuming the session, here is the opportunity to set breakpoints
// Resume execution of all threads associated with the session
err = session.ResumeAll()
if err != nil {
// handle error
}
Managing Breakpoints
Breakpoints can be added, removed, and enumerated.
Breakpoints are added to file-and-row locations in a module, and are triggered when the policy evaluation reaches that location. Breakpoints can be added at any time during policy evaluation.
// Add a breakpoint
br, err := session.AddBreakpoint(location.Location{
File: "/path/to/policy.rego",
Row: 10,
})
if err != nil {
// handle error
}
// ...
// Remove the breakpoint
_, err = session.RemoveBreakpoint(br.ID)
if err != nil {
// handle error
}
Stepping Through Policy Evaluation
When evaluation execution is paused, either immidiately after launching a session or when a breakpoint is hit, the session can be stepped through.
Step Over
StepOver() executes the next expression in the current scope and then stops on the next expression in the same scope,
not stopping on expressions in sub-scopes; e.g. execution of referenced rule, called function, comprehension, or every expression.
threads, err := session.Threads()
if err != nil {
// handle error
}
if err := session.StepOver(threads[0].ID); err != nil {
// handle error
}
Example 1
allow if {
x := f(input) >-+
x == 1 |
} |
|
f(x) := y if { <-+
y := x + 1
}
Example 2
allow if {
every x in l { >-+
x < 10 <-+
}
input.x == 1
Step In
StepIn() executes the next expression in the current scope and then stops on the next expression in the same scope or sub-scope;
stepping into any referenced rule, called function, comprehension, or every expression.
if err := session.StepIn(threads[0].ID); err != nil {
// handle error
}
Example 1
allow if {
x := f(input) >-+
x == 1 |
} |
|
f(x) := y if { <-+
y := x + 1
}
Example 2
allow if {
every x in l { >-+
x < 10 <-+
}
input.x == 1
}
Step Out
StepOut() steps out of the current scope (rule, function, comprehension, every expression) and stops on the next expression in the parent scope.
if err := session.StepOut(threads[0].ID); err != nil {
// handle error
}
Example 1
allow if {
x := f(input) <-+
x == 1 |
} |
|
f(x) := y if { |
y := x + 1 >-+
}
Example 2
allow if {
every x in l {
x < 10 >-+
} |
input.x == 1 <-+
}
Fetching Variable Values
The current values of local and global variables are organized into scopes:
Local: contains variables defined in the current rule, function, comprehension, or every expression.Virtual Cache: contains the state of the global Virtual Cache, where calculated return values for rules and functions are stored.Input: contains the input document.Data: contains the data document.Result Set: contains the result set of the current query. This scope is only available on the final expression of the query evaluation.
scopes, err := session.Scopes(thread.ID)
if err != nil {
// handle error
}
var localScope debug.Scope
for _, scope := range scopes {
if scope.Name == "Local" {
localScope = scope
break
}
}
variables, err := session.Variables(localScope.VariablesReference())
if err != nil {
// handle error
}
// Enumerate and process variables