Files
Anders Eknert 3e1af3bd6a perf: String() implementations using appenders (#8192)
Some work I did during the holidays as part of improving the performance
of interpolated strings. This change is however not isolated to those, but
updates the `String()` implementation of all AST node types (term values
and policy components). This change also lays the groundwork for migrating
OPA to the `json/v2` package once that's stable. The `json/v2` package
provides low-level functions for zero alloc marshalling via appenders — and
well, here they are. The appenders here should be usable for that purpose with
only a few tweaks needed for the few cases where our `String()` implementations
aren't also valid JSON.

Creating perfectly sized buffers requires knowing the expected length beforehand.
In order to do this, each component now implements not only `encoding.AppendText`
but a new custom `StringLengther` interface, which allows asking any AST node about
its `StringLength()` before `make`ing a buffer of that length.

We could definitely consider adding these to e.g. the `Value` or `Node` interfaces,
but I've left that out of this PR as it's an easy thing to do later should we want
to, and I guess there's always some concerns about changing public interfaces even
when they're not meant to be implemented by external code.

While no `Value` appenders allocate and almost none of the policy appenders do either,
one notable exception is `Module` when there are annotations present, as they are
a bit of a (YAML) special case. It's doable, but as serializing full modules isn't
on a hot path anywhere, I have chosen to defer that work to the future.

Signed-off-by: Anders Eknert <anders.eknert@apple.com>
2026-01-08 10:14:53 +01:00

137 lines
2.8 KiB
Go

package topdown_test
import (
"errors"
"fmt"
"testing"
"github.com/open-policy-agent/opa/v1/ast/location"
"github.com/open-policy-agent/opa/v1/topdown"
)
func TestErrorWrapping(t *testing.T) {
t.Parallel()
isHalt := func(err error) bool {
return errors.As(err, &topdown.Halt{})
}
builtinErr := errors.New("builtin error")
loc := location.Location{
File: "b.rego",
Col: 10,
Row: 12,
}
e0 := (&topdown.Error{Code: topdown.BuiltinErr,
Message: "builtin error",
Location: &loc,
}).Wrap(builtinErr)
tests := []struct {
note string
err error
check func(error) bool
}{
{
note: "plain",
err: &topdown.Error{},
check: topdown.IsError,
},
{
note: "wrapped",
err: fmt.Errorf("meh: %w", &topdown.Error{}),
check: topdown.IsError,
},
{
note: "wrapped in Halt",
err: topdown.Halt{Err: &topdown.Error{}},
check: topdown.IsError,
},
{
note: "check for Halt",
err: topdown.Halt{Err: &topdown.Error{}},
check: isHalt,
},
{
note: "check for Halt, wrapped",
err: fmt.Errorf("meh: %w", topdown.Halt{Err: &topdown.Error{}}),
check: isHalt,
},
{
note: "plain cancel",
err: &topdown.Error{Code: topdown.CancelErr},
check: topdown.IsCancel,
},
{
note: "wrapped cancel",
err: fmt.Errorf("meh: %w", &topdown.Error{Code: topdown.CancelErr}),
check: topdown.IsCancel,
},
{
note: "wrapped builtin error",
err: e0,
check: func(err error) bool {
return errors.Is(err, builtinErr)
},
},
{
note: "matching errors, code",
err: e0,
check: func(err error) bool {
return errors.Is(err, &topdown.Error{Code: topdown.BuiltinErr})
},
},
{
note: "matching errors, code and message",
err: e0,
check: func(err error) bool {
return errors.Is(err, &topdown.Error{Code: topdown.BuiltinErr, Message: "builtin error"})
},
},
{
note: "matching errors, code, message and location",
err: e0,
check: func(err error) bool {
return errors.Is(err, &topdown.Error{Code: topdown.BuiltinErr, Message: "builtin error", Location: &loc})
},
},
{
note: "matching errors, code, message, location and builtin error",
err: e0,
check: func(err error) bool {
return errors.Is(err, e0)
},
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
if !tc.check(tc.err) {
t.Errorf("unexpected 'false'")
}
})
}
}
// 101.7 ns/op 144 B/op 5 allocs/op // using fmt.Sprintf
// 18.78 ns/op 48 B/op 1 allocs/op // using []byte + Location.AppendText
func BenchmarkErrorError(b *testing.B) {
loc := &location.Location{
File: "b.rego",
Col: 10,
Row: 12,
}
err := &topdown.Error{
Code: topdown.BuiltinErr,
Message: "builtin error",
Location: loc,
}
for b.Loop() {
_ = err.Error()
}
}