build(deps): bump github.com/open-policy-agent/opa from 1.12.3 to 1.13.1
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 1.12.3 to 1.13.1. - [Release notes](https://github.com/open-policy-agent/opa/releases) - [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-policy-agent/opa/compare/v1.12.3...v1.13.1) --- updated-dependencies: - dependency-name: github.com/open-policy-agent/opa dependency-version: 1.13.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
4e9eb596f0
commit
5daeada697
+4916
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+3
@@ -111,6 +111,9 @@ opa_arith_rem,opa_bf_to_number
|
||||
opa_array_concat,opa_value_type
|
||||
opa_array_concat,opa_array_with_cap
|
||||
opa_array_concat,opa_array_append
|
||||
opa_array_flatten,opa_value_type
|
||||
opa_array_flatten,opa_array_with_cap
|
||||
opa_array_flatten,opa_array_append
|
||||
opa_array_slice,opa_value_type
|
||||
opa_array_slice,opa_number_try_int
|
||||
opa_array_slice,opa_array_with_cap
|
||||
|
||||
|
BIN
Binary file not shown.
+1
@@ -90,6 +90,7 @@ var builtinsFunctions = map[string]string{
|
||||
ast.Floor.Name: "opa_arith_floor",
|
||||
ast.Rem.Name: "opa_arith_rem",
|
||||
ast.ArrayConcat.Name: "opa_array_concat",
|
||||
ast.ArrayFlatten.Name: "opa_array_flatten",
|
||||
ast.ArrayReverse.Name: "opa_array_reverse",
|
||||
ast.ArraySlice.Name: "opa_array_slice",
|
||||
ast.SetDiff.Name: "opa_set_diff",
|
||||
|
||||
+3
-3
@@ -602,7 +602,7 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
}
|
||||
return child.Unfold(path[1:])
|
||||
}
|
||||
return nil, fmt.Errorf("path %v does not exist in object term %v", ast.Ref{path[0]}, e.value.Value)
|
||||
return nil, fmt.Errorf("path %v does not exist in object term %v", path[0], e.value.Value)
|
||||
case ast.Set:
|
||||
// Sets' keys *are* their values, so in order to allow accurate
|
||||
// traversal, we have to collapse the tree beneath this node,
|
||||
@@ -662,10 +662,10 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
}
|
||||
return child.Unfold(path[1:])
|
||||
}
|
||||
return nil, fmt.Errorf("path %v does not exist in array term %v", ast.Ref{ast.IntNumberTerm(idx)}, e.value.Value)
|
||||
return nil, fmt.Errorf("path %v does not exist in array term %v", ast.IntNumberTerm(idx), e.value.Value)
|
||||
default:
|
||||
// Catch all primitive types.
|
||||
return nil, fmt.Errorf("expected composite type for path %v, found value: %v (type: %T)", ast.Ref{path[0]}, x, x)
|
||||
return nil, fmt.Errorf("expected composite type for path %v, found value: %v (type: %T)", path[0], x, x)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+31
-35
@@ -2,11 +2,10 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package report provides functions to report OPA's version information to an external service and process the response.
|
||||
package report
|
||||
// Package versioncheck provides functions to check for the latest OPA release version from GitHub.
|
||||
package versioncheck
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -34,24 +32,24 @@ import (
|
||||
//
|
||||
// Override at build time via:
|
||||
//
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/report.ExternalServiceURL=<url>"
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/report.GHRepo=<url>"
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/versioncheck.ExternalServiceURL=<url>"
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/versioncheck.GHRepo=<url>"
|
||||
//
|
||||
// ExternalServiceURL will be overridden if the OPA_TELEMETRY_SERVICE_URL environment variable
|
||||
// ExternalServiceURL will be overridden if the OPA_VERSION_CHECK_SERVICE_URL environment variable
|
||||
// is provided.
|
||||
var ExternalServiceURL = "https://api.github.com"
|
||||
var GHRepo = "open-policy-agent/opa"
|
||||
|
||||
// Reporter reports information such as the version, heap usage about the running OPA instance to an external service
|
||||
type Reporter interface {
|
||||
SendReport(ctx context.Context) (*DataResponse, error)
|
||||
// Checker checks for the latest OPA release version
|
||||
type Checker interface {
|
||||
LatestVersion(ctx context.Context) (*DataResponse, error)
|
||||
RegisterGatherer(key string, f Gatherer)
|
||||
}
|
||||
|
||||
// Gatherer represents a mechanism to inject additional data in the telemetry report
|
||||
// Gatherer represents a mechanism to inject additional data (currently unused for version checking)
|
||||
type Gatherer func(ctx context.Context) (any, error)
|
||||
|
||||
// DataResponse represents the data returned by the external service
|
||||
// DataResponse represents the data returned by the version check
|
||||
type DataResponse struct {
|
||||
Latest ReleaseDetails `json:"latest"`
|
||||
}
|
||||
@@ -64,44 +62,48 @@ type ReleaseDetails struct {
|
||||
OPAUpToDate bool `json:"opa_up_to_date,omitempty"` // is running OPA version greater than or equal to the latest released
|
||||
}
|
||||
|
||||
// Options supplies parameters to the reporter.
|
||||
// Options supplies parameters to the version checker.
|
||||
type Options struct {
|
||||
Logger logging.Logger
|
||||
}
|
||||
|
||||
type GHVersionCollector struct {
|
||||
type GitHubVersionChecker struct {
|
||||
client rest.Client
|
||||
}
|
||||
|
||||
type GHResponse struct {
|
||||
type GitHubRelease struct {
|
||||
TagName string `json:"tag_name,omitempty"` // latest OPA release tag
|
||||
ReleaseNotes string `json:"html_url,omitempty"` // link to the OPA release notes
|
||||
Download string `json:"assets_url,omitempty"` // link to download the OPA release
|
||||
}
|
||||
|
||||
// New returns an instance of the Reporter
|
||||
func New(opts Options) (Reporter, error) {
|
||||
url := cmp.Or(os.Getenv("OPA_TELEMETRY_SERVICE_URL"), ExternalServiceURL)
|
||||
// New returns an instance of the Checker
|
||||
func New(opts Options) (Checker, error) {
|
||||
url := os.Getenv("OPA_VERSION_CHECK_SERVICE_URL")
|
||||
if url == "" {
|
||||
url = ExternalServiceURL
|
||||
}
|
||||
|
||||
// Set a generic User-Agent to avoid sending version/platform information about the user's OPA instance.
|
||||
// This ensures we only retrieve version information without transmitting any identifying data.
|
||||
restConfig := fmt.Appendf(nil, `{
|
||||
"url": %q,
|
||||
"headers": {
|
||||
"User-Agent": "OPA-Version-Checker"
|
||||
}
|
||||
}`, url)
|
||||
|
||||
client, err := rest.New(restConfig, map[string]*keys.Config{}, rest.Logger(opts.Logger))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := GHVersionCollector{client: client}
|
||||
|
||||
// heap_usage_bytes is always present, so register it unconditionally
|
||||
r.RegisterGatherer("heap_usage_bytes", readRuntimeMemStats)
|
||||
r := GitHubVersionChecker{client: client}
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// SendReport sends the telemetry report which includes information such as the OPA version, current memory usage to
|
||||
// the external service
|
||||
func (r *GHVersionCollector) SendReport(ctx context.Context) (*DataResponse, error) {
|
||||
// LatestVersion queries the GitHub API to check for the latest OPA release version
|
||||
func (r *GitHubVersionChecker) LatestVersion(ctx context.Context) (*DataResponse, error) {
|
||||
rCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -115,12 +117,12 @@ func (r *GHVersionCollector) SendReport(ctx context.Context) (*DataResponse, err
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
if resp.Body != nil {
|
||||
var result GHResponse
|
||||
var result GitHubRelease
|
||||
err := json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createDataResponse(result)
|
||||
return createReleaseInfo(result)
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
@@ -128,7 +130,7 @@ func (r *GHVersionCollector) SendReport(ctx context.Context) (*DataResponse, err
|
||||
}
|
||||
}
|
||||
|
||||
func createDataResponse(ghResp GHResponse) (*DataResponse, error) {
|
||||
func createReleaseInfo(ghResp GitHubRelease) (*DataResponse, error) {
|
||||
if ghResp.TagName == "" {
|
||||
return nil, errors.New("server response does not contain tag_name")
|
||||
}
|
||||
@@ -168,7 +170,7 @@ func createDataResponse(ghResp GHResponse) (*DataResponse, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*GHVersionCollector) RegisterGatherer(_ string, _ Gatherer) {
|
||||
func (*GitHubVersionChecker) RegisterGatherer(_ string, _ Gatherer) {
|
||||
// no-op for this implementation
|
||||
}
|
||||
|
||||
@@ -206,9 +208,3 @@ func (dr *DataResponse) Pretty() string {
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func readRuntimeMemStats(_ context.Context) (any, error) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
return strconv.FormatUint(m.Alloc, 10), nil
|
||||
}
|
||||
+8
@@ -408,6 +408,14 @@ func Store(s storage.Store) func(r *Rego) {
|
||||
return v1.Store(s)
|
||||
}
|
||||
|
||||
// Data returns an argument that sets the Rego data document. Data should be
|
||||
// a map representing the data document. This is a simpler alternative to
|
||||
// using Store with inmem.NewFromObject for cases where an in-memory store
|
||||
// with static data is sufficient.
|
||||
func Data(x map[string]any) func(r *Rego) {
|
||||
return v1.Data(x)
|
||||
}
|
||||
|
||||
// StoreReadAST returns an argument that sets whether the store should eagerly convert data to AST values.
|
||||
//
|
||||
// Only applicable when no store has been set on the Rego object through the Store option.
|
||||
|
||||
+34
-20
@@ -95,6 +95,7 @@ var DefaultBuiltins = [...]*Builtin{
|
||||
|
||||
// Arrays
|
||||
ArrayConcat,
|
||||
ArrayFlatten,
|
||||
ArraySlice,
|
||||
ArrayReverse,
|
||||
|
||||
@@ -893,6 +894,18 @@ var ArrayConcat = &Builtin{
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
var ArrayFlatten = &Builtin{
|
||||
Name: "array.flatten",
|
||||
Description: "Non-recursively unpacks array items in arr into the flattened array. Other types are appended as-is.",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.Named("arr", types.NewArray(nil, types.A)).Description("the array to be flattened"),
|
||||
),
|
||||
types.Named("flattened", types.NewArray(nil, types.A)).Description("array flattened one level"),
|
||||
),
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
var ArraySlice = &Builtin{
|
||||
Name: "array.slice",
|
||||
Description: "Returns a slice of a given array. If `start` is greater or equal than `stop`, `slice` is `[]`.",
|
||||
@@ -1823,7 +1836,8 @@ var ObjectKeys = &Builtin{
|
||||
/*
|
||||
* Encoding
|
||||
*/
|
||||
var encoding = category("encoding")
|
||||
// Not using 'encoding' to avoid having to alias stdlib "encoding" imports
|
||||
var catEncoding = category("encoding")
|
||||
|
||||
var JSONMarshal = &Builtin{
|
||||
Name: "json.marshal",
|
||||
@@ -1834,7 +1848,7 @@ var JSONMarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the JSON string representation of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1856,7 +1870,7 @@ var JSONMarshalWithOptions = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the JSON string representation of `x`, with configured prefix/indent string(s) as appropriate"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1869,7 +1883,7 @@ var JSONUnmarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.A).Description("the term deserialized from `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1882,7 +1896,7 @@ var JSONIsValid = &Builtin{
|
||||
),
|
||||
types.Named("result", types.B).Description("`true` if `x` is valid JSON, `false` otherwise"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1895,7 +1909,7 @@ var Base64Encode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64 serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1908,7 +1922,7 @@ var Base64Decode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64 deserialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1921,7 +1935,7 @@ var Base64IsValid = &Builtin{
|
||||
),
|
||||
types.Named("result", types.B).Description("`true` if `x` is valid base64 encoded value, `false` otherwise"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1934,7 +1948,7 @@ var Base64UrlEncode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64url serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1947,7 +1961,7 @@ var Base64UrlEncodeNoPad = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64url serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1960,7 +1974,7 @@ var Base64UrlDecode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64url deserialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1973,7 +1987,7 @@ var URLQueryDecode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("URL-encoding deserialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1986,7 +2000,7 @@ var URLQueryEncode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("URL-encoding serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2010,7 +2024,7 @@ var URLQueryEncodeObject = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the URL-encoded serialization of `object`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2025,7 +2039,7 @@ var URLQueryDecodeObject = &Builtin{
|
||||
types.S,
|
||||
types.NewArray(nil, types.S)))).Description("the resulting object"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2038,7 +2052,7 @@ var YAMLMarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the YAML string representation of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2051,7 +2065,7 @@ var YAMLUnmarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.A).Description("the term deserialized from `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2065,7 +2079,7 @@ var YAMLIsValid = &Builtin{
|
||||
),
|
||||
types.Named("result", types.B).Description("`true` if `x` is valid YAML, `false` otherwise"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2078,7 +2092,7 @@ var HexEncode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("serialization of `x` using hex-encoding"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2091,7 +2105,7 @@ var HexDecode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("deserialized from `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
|
||||
+22
-5
@@ -6,6 +6,7 @@ package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -387,10 +388,12 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
|
||||
return NewError(TypeErr, expr.Location, "undefined function %v", name)
|
||||
}
|
||||
|
||||
// check if the expression refers to a function that contains an error
|
||||
_, ok := tpe.(types.Any)
|
||||
if ok {
|
||||
return nil
|
||||
if t, ok := tpe.(types.Any); ok {
|
||||
// A type.Any with a len(0) is created by using types.A , this represents a potential non-local reference
|
||||
// This is the exception when checking if the type represents a function
|
||||
if len(t) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ftpe, ok := tpe.(*types.Function)
|
||||
@@ -1087,7 +1090,21 @@ func newRefErrInvalid(loc *Location, ref Ref, idx int, have, want types.Type, on
|
||||
}
|
||||
|
||||
func newRefErrUnsupported(loc *Location, ref Ref, idx int, have types.Type) *Error {
|
||||
err := newRefError(loc, ref)
|
||||
var err *Error
|
||||
switch have.(type) {
|
||||
case *types.Function:
|
||||
var function string
|
||||
// drop any trailing references to unidentified parameters (e.g. __local1__)
|
||||
if match, err := regexp.MatchString(`__local[0-9]+__`, ref[len(ref)-1].Value.String()); err == nil && match {
|
||||
function = ref[:len(ref)-1].String()
|
||||
} else {
|
||||
function = ref.String()
|
||||
}
|
||||
|
||||
err = NewError(TypeErr, loc, "function %s used as reference, not called", function)
|
||||
default:
|
||||
err = newRefError(loc, ref)
|
||||
}
|
||||
err.Details = &RefErrUnsupportedDetail{
|
||||
Ref: ref,
|
||||
Pos: idx,
|
||||
|
||||
+2
@@ -162,6 +162,8 @@ func (env *TypeEnv) getRefFallback(ref Ref) types.Type {
|
||||
}
|
||||
|
||||
if RootDocumentNames.Contains(ref[0]) {
|
||||
// types.A is an empty types.Any
|
||||
// this is used to represent a potential non-local reference
|
||||
return types.A
|
||||
}
|
||||
|
||||
|
||||
+32
-9
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// Location records a position in source code
|
||||
@@ -28,10 +29,10 @@ func NewLocation(text []byte, file string, row int, col int) *Location {
|
||||
|
||||
// Equal checks if two locations are equal to each other.
|
||||
func (loc *Location) Equal(other *Location) bool {
|
||||
return bytes.Equal(loc.Text, other.Text) &&
|
||||
loc.File == other.File &&
|
||||
return loc.File == other.File &&
|
||||
loc.Row == other.Row &&
|
||||
loc.Col == other.Col
|
||||
loc.Col == other.Col &&
|
||||
bytes.Equal(loc.Text, other.Text)
|
||||
}
|
||||
|
||||
// Errorf returns a new error value with a message formatted to include the location
|
||||
@@ -57,13 +58,35 @@ func (loc *Location) Format(f string, a ...any) string {
|
||||
}
|
||||
|
||||
func (loc *Location) String() string {
|
||||
if len(loc.File) > 0 {
|
||||
return fmt.Sprintf("%v:%v", loc.File, loc.Row)
|
||||
buf, _ := loc.AppendText(make([]byte, 0, loc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (loc *Location) AppendText(buf []byte) ([]byte, error) {
|
||||
if loc != nil {
|
||||
switch {
|
||||
case len(loc.File) > 0:
|
||||
buf = util.AppendInt(append(append(buf, loc.File...), ':'), loc.Row)
|
||||
case len(loc.Text) > 0:
|
||||
buf = append(buf, loc.Text...)
|
||||
default:
|
||||
buf = util.AppendInt(append(util.AppendInt(buf, loc.Row), ':'), loc.Col)
|
||||
}
|
||||
}
|
||||
if len(loc.Text) > 0 {
|
||||
return string(loc.Text)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (loc *Location) StringLength() (n int) {
|
||||
if loc != nil {
|
||||
if l := len(loc.File); l > 0 {
|
||||
n = l + 1 + util.NumDigitsInt(loc.Row)
|
||||
} else if l := len(loc.Text); l > 0 {
|
||||
n = l
|
||||
} else {
|
||||
n = util.NumDigitsInt(loc.Row) + 1 + util.NumDigitsInt(loc.Col)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%v:%v", loc.Row, loc.Col)
|
||||
return n
|
||||
}
|
||||
|
||||
// Compare returns -1, 0, or 1 to indicate if this loc is less than, equal to,
|
||||
@@ -71,7 +94,7 @@ func (loc *Location) String() string {
|
||||
// column of the Location (but not on the text.) Nil locations are greater than
|
||||
// non-nil locations.
|
||||
func (loc *Location) Compare(other *Location) int {
|
||||
if loc == nil && other == nil {
|
||||
if loc == other {
|
||||
return 0
|
||||
} else if loc == nil {
|
||||
return 1
|
||||
|
||||
+91
-83
@@ -20,7 +20,7 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/scanner"
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/tokens"
|
||||
@@ -71,6 +71,10 @@ var (
|
||||
// copy them to the call term only when needed
|
||||
memberWithKeyRef = MemberWithKey.Ref()
|
||||
memberRef = Member.Ref()
|
||||
|
||||
newlineBytes = []byte{'\n'}
|
||||
metadataBytes = []byte("METADATA")
|
||||
metadataParserPool = util.NewSyncPool[metadataParser]()
|
||||
)
|
||||
|
||||
func (v RegoVersion) Int() int {
|
||||
@@ -540,44 +544,46 @@ func (p *Parser) parseAnnotations(stmts []Statement) []Statement {
|
||||
return stmts
|
||||
}
|
||||
|
||||
func parseAnnotations(comments []*Comment) ([]*Annotations, Errors) {
|
||||
func parseAnnotations(comments []*Comment) (stmts []*Annotations, errs Errors) {
|
||||
numBlocks := CountFunc(comments, isMetadataComment)
|
||||
if numBlocks == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var hint = []byte("METADATA")
|
||||
var curr *metadataParser
|
||||
var blocks []*metadataParser
|
||||
stmts = make([]*Annotations, 0, numBlocks)
|
||||
mdp := metadataParserPool.Get()
|
||||
if mdp.buf == nil {
|
||||
mdp.buf = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
for i := range comments {
|
||||
if curr != nil {
|
||||
if comments[i].Location.Row == comments[i-1].Location.Row+1 && comments[i].Location.Col == 1 {
|
||||
curr.Append(comments[i])
|
||||
continue
|
||||
if isMetadataComment(comments[i]) { // scan until end of block
|
||||
mdp.Reset(comments[i].Location)
|
||||
for i++; i < len(comments) && !blockBuster(comments[i], comments[i-1]); i++ {
|
||||
mdp.Append(comments[i])
|
||||
}
|
||||
|
||||
if a, err := mdp.Parse(); err != nil {
|
||||
errs = append(errs, &Error{Code: ParseErr, Message: err.Error(), Location: mdp.loc})
|
||||
} else {
|
||||
stmts = append(stmts, a)
|
||||
}
|
||||
curr = nil
|
||||
}
|
||||
if bytes.HasPrefix(bytes.TrimSpace(comments[i].Text), hint) {
|
||||
curr = newMetadataParser(comments[i].Location)
|
||||
blocks = append(blocks, curr)
|
||||
}
|
||||
}
|
||||
|
||||
stmts := make([]*Annotations, 0, len(blocks))
|
||||
|
||||
var errs Errors
|
||||
for _, b := range blocks {
|
||||
if a, err := b.Parse(); err != nil {
|
||||
errs = append(errs, &Error{
|
||||
Code: ParseErr,
|
||||
Message: err.Error(),
|
||||
Location: b.loc,
|
||||
})
|
||||
} else {
|
||||
stmts = append(stmts, a)
|
||||
}
|
||||
}
|
||||
metadataParserPool.Put(mdp)
|
||||
|
||||
return stmts, errs
|
||||
}
|
||||
|
||||
func isMetadataComment(c *Comment) bool {
|
||||
return c.Location.Col == 1 && bytes.HasPrefix(bytes.TrimSpace(c.Text), metadataBytes)
|
||||
}
|
||||
|
||||
func blockBuster(curr, prev *Comment) bool { // or endOfBlock, but the name was too good to pass up
|
||||
return curr.Location.Col != 1 || curr.Location.Row-1 != prev.Location.Row
|
||||
}
|
||||
|
||||
func (p *Parser) parsePackage() *Package {
|
||||
if p.s.tok != tokens.Package {
|
||||
return nil
|
||||
@@ -2455,7 +2461,8 @@ func (p *Parser) parseTermPairList(end tokens.Token, r [][2]*Term) [][2]*Term {
|
||||
|
||||
func (p *Parser) parseTermOp(values ...tokens.Token) *Term {
|
||||
if slices.Contains(values, p.s.tok) {
|
||||
r := RefTerm(VarTerm(p.s.tok.String()).SetLocation(p.s.Loc())).SetLocation(p.s.Loc())
|
||||
loc := p.s.Loc()
|
||||
r := RefTerm(VarTerm(p.s.tok.String()).SetLocation(loc)).SetLocation(loc)
|
||||
p.scan()
|
||||
return r
|
||||
}
|
||||
@@ -2465,11 +2472,12 @@ func (p *Parser) parseTermOp(values ...tokens.Token) *Term {
|
||||
func (p *Parser) parseTermOpName(ref Ref, values ...tokens.Token) *Term {
|
||||
if slices.Contains(values, p.s.tok) {
|
||||
cp := ref.Copy()
|
||||
loc := p.s.Loc()
|
||||
for _, r := range cp {
|
||||
r.SetLocation(p.s.Loc())
|
||||
r.SetLocation(loc)
|
||||
}
|
||||
t := RefTerm(cp...)
|
||||
t.SetLocation(p.s.Loc())
|
||||
t.SetLocation(loc)
|
||||
p.scan()
|
||||
return t
|
||||
}
|
||||
@@ -2743,13 +2751,17 @@ type rawAnnotation struct {
|
||||
}
|
||||
|
||||
type metadataParser struct {
|
||||
buf *bytes.Buffer
|
||||
comments []*Comment
|
||||
buf *bytes.Buffer
|
||||
loc *location.Location
|
||||
}
|
||||
|
||||
func newMetadataParser(loc *Location) *metadataParser {
|
||||
return &metadataParser{loc: loc, buf: bytes.NewBuffer(nil)}
|
||||
func (b *metadataParser) Reset(loc *location.Location) {
|
||||
b.comments = b.comments[:0]
|
||||
b.loc = loc
|
||||
if b.buf != nil {
|
||||
b.buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *metadataParser) Append(c *Comment) {
|
||||
@@ -2760,14 +2772,12 @@ func (b *metadataParser) Append(c *Comment) {
|
||||
|
||||
var yamlLineErrRegex = regexp.MustCompile(`^yaml:(?: unmarshal errors:[\n\s]*)? line ([[:digit:]]+):`)
|
||||
|
||||
func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
|
||||
var raw rawAnnotation
|
||||
|
||||
func (b *metadataParser) Parse() (result *Annotations, err error) {
|
||||
if len(bytes.TrimSpace(b.buf.Bytes())) == 0 {
|
||||
return nil, errors.New("expected METADATA block, found whitespace")
|
||||
}
|
||||
|
||||
var raw rawAnnotation
|
||||
if err := yaml.Unmarshal(b.buf.Bytes(), &raw); err != nil {
|
||||
var comment *Comment
|
||||
match := yamlLineErrRegex.FindStringSubmatch(err.Error())
|
||||
@@ -2790,13 +2800,14 @@ func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
return nil, augmentYamlError(err, b.comments)
|
||||
}
|
||||
|
||||
var result Annotations
|
||||
result.comments = b.comments
|
||||
result.Scope = raw.Scope
|
||||
result.Entrypoint = raw.Entrypoint
|
||||
result.Title = raw.Title
|
||||
result.Description = raw.Description
|
||||
result.Organizations = raw.Organizations
|
||||
result = &Annotations{
|
||||
comments: b.comments,
|
||||
Scope: raw.Scope,
|
||||
Entrypoint: raw.Entrypoint,
|
||||
Title: raw.Title,
|
||||
Description: raw.Description,
|
||||
Organizations: raw.Organizations,
|
||||
}
|
||||
|
||||
for _, v := range raw.RelatedResources {
|
||||
rr, err := parseRelatedResource(v)
|
||||
@@ -2878,32 +2889,30 @@ func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
result.Authors = append(result.Authors, author)
|
||||
}
|
||||
|
||||
result.Custom = make(map[string]any)
|
||||
for k, v := range raw.Custom {
|
||||
val, err := convertYAMLMapKeyTypes(v, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if raw.Custom != nil {
|
||||
result.Custom = make(map[string]any, len(raw.Custom))
|
||||
for k, v := range raw.Custom {
|
||||
if result.Custom[k], err = convertYAMLMapKeyTypes(v, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
result.Custom[k] = val
|
||||
}
|
||||
|
||||
result.Location = b.loc
|
||||
|
||||
// recreate original text of entire metadata block for location text attribute
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("# METADATA\n")
|
||||
original := bytes.TrimSuffix(b.buf.Bytes(), newlineBytes)
|
||||
numLines := bytes.Count(original, newlineBytes) + 1
|
||||
preAlloc := len("# METADATA\n") + len(original) + numLines*2 // '# ' prefix added per line
|
||||
|
||||
lines := bytes.Split(b.buf.Bytes(), []byte{'\n'})
|
||||
result.Location.Text = append(make([]byte, 0, preAlloc), "# METADATA\n"...)
|
||||
|
||||
for _, line := range lines[:len(lines)-1] {
|
||||
sb.WriteString("# ")
|
||||
sb.Write(line)
|
||||
sb.WriteByte('\n')
|
||||
for line := range bytes.SplitAfterSeq(original, newlineBytes) {
|
||||
result.Location.Text = append(result.Location.Text, "# "...)
|
||||
result.Location.Text = append(result.Location.Text, line...)
|
||||
}
|
||||
|
||||
result.Location.Text = []byte(strings.TrimSuffix(sb.String(), "\n"))
|
||||
|
||||
return &result, nil
|
||||
return result, err
|
||||
}
|
||||
|
||||
// augmentYamlError augments a YAML error with hints intended to help the user figure out the cause of an otherwise
|
||||
@@ -2912,30 +2921,29 @@ func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
func augmentYamlError(err error, comments []*Comment) error {
|
||||
// Adding hints for when key/value ':' separator isn't suffixed with a legal YAML space symbol
|
||||
for _, comment := range comments {
|
||||
txt := string(comment.Text)
|
||||
parts := strings.Split(txt, ":")
|
||||
if len(parts) > 1 {
|
||||
parts = parts[1:]
|
||||
var invalidSpaces []string
|
||||
for partIndex, part := range parts {
|
||||
if len(part) == 0 && partIndex == len(parts)-1 {
|
||||
invalidSpaces = []string{}
|
||||
break
|
||||
}
|
||||
if bytes.IndexByte(comment.Text, ':') == -1 {
|
||||
continue
|
||||
}
|
||||
parts := bytes.Split(comment.Text, []byte{':'})[1:]
|
||||
|
||||
r, _ := utf8.DecodeRuneInString(part)
|
||||
if r == ' ' || r == '\t' {
|
||||
invalidSpaces = []string{}
|
||||
break
|
||||
}
|
||||
var invalidSpaces []string
|
||||
for partIndex, part := range parts {
|
||||
if len(part) == 0 && partIndex == len(parts)-1 {
|
||||
break
|
||||
}
|
||||
|
||||
invalidSpaces = append(invalidSpaces, fmt.Sprintf("%+q", r))
|
||||
}
|
||||
if len(invalidSpaces) > 0 {
|
||||
err = fmt.Errorf(
|
||||
"%s\n Hint: on line %d, symbol(s) %v immediately following a key/value separator ':' is not a legal yaml space character",
|
||||
err.Error(), comment.Location.Row, invalidSpaces)
|
||||
r, _ := utf8.DecodeRune(part)
|
||||
if r == ' ' || r == '\t' {
|
||||
break
|
||||
}
|
||||
|
||||
invalidSpaces = append(invalidSpaces, fmt.Sprintf("%+q", r))
|
||||
}
|
||||
if len(invalidSpaces) > 0 {
|
||||
err = fmt.Errorf(
|
||||
"%s\n Hint: on line %d, symbol(s) %v immediately following a"+
|
||||
" key/value separator ':' is not a legal yaml space character",
|
||||
err.Error(), comment.Location.Row, invalidSpaces)
|
||||
}
|
||||
}
|
||||
return err
|
||||
@@ -3053,7 +3061,7 @@ func parseAuthorString(s string) (*AuthorAnnotation, error) {
|
||||
if len(trailing) >= len(emailPrefix)+len(emailSuffix) && strings.HasPrefix(trailing, emailPrefix) &&
|
||||
strings.HasSuffix(trailing, emailSuffix) {
|
||||
email = trailing[len(emailPrefix):]
|
||||
email = email[0 : len(email)-len(emailSuffix)]
|
||||
email = email[:len(email)-len(emailSuffix)]
|
||||
namePartCount -= 1
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -4,6 +4,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -83,3 +84,16 @@ func BuiltinNameFromRef(ref Ref) (string, bool) {
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func AppendDelimeted[T encoding.TextAppender](buf []byte, appenders []T, delim string) ([]byte, error) {
|
||||
for i, item := range appenders {
|
||||
if i > 0 {
|
||||
buf = append(buf, delim...)
|
||||
}
|
||||
var err error
|
||||
if buf, err = item.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
+75
-194
@@ -371,42 +371,8 @@ func (mod *Module) Equal(other *Module) bool {
|
||||
}
|
||||
|
||||
func (mod *Module) String() string {
|
||||
byNode := map[Node][]*Annotations{}
|
||||
for _, a := range mod.Annotations {
|
||||
byNode[a.node] = append(byNode[a.node], a)
|
||||
}
|
||||
|
||||
appendAnnotationStrings := func(buf []string, node Node) []string {
|
||||
if as, ok := byNode[node]; ok {
|
||||
for i := range as {
|
||||
buf = append(buf,
|
||||
"# METADATA",
|
||||
"# "+as[i].String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
buf := []string{}
|
||||
buf = appendAnnotationStrings(buf, mod.Package)
|
||||
buf = append(buf, mod.Package.String())
|
||||
|
||||
if len(mod.Imports) > 0 {
|
||||
buf = append(buf, "")
|
||||
for _, imp := range mod.Imports {
|
||||
buf = appendAnnotationStrings(buf, imp)
|
||||
buf = append(buf, imp.String())
|
||||
}
|
||||
}
|
||||
if len(mod.Rules) > 0 {
|
||||
buf = append(buf, "")
|
||||
for _, rule := range mod.Rules {
|
||||
buf = appendAnnotationStrings(buf, rule)
|
||||
buf = append(buf, rule.stringWithOpts(toStringOpts{regoVersion: mod.regoVersion}))
|
||||
}
|
||||
}
|
||||
return strings.Join(buf, "\n")
|
||||
buf, _ := mod.AppendText(make([]byte, 0, mod.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// RuleSet returns a RuleSet containing named rules in the mod.
|
||||
@@ -475,7 +441,8 @@ func (c *Comment) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (c *Comment) String() string {
|
||||
return "#" + string(c.Text)
|
||||
buf, _ := c.AppendText(make([]byte, 0, c.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of c.
|
||||
@@ -525,16 +492,8 @@ func (pkg *Package) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (pkg *Package) String() string {
|
||||
if pkg == nil {
|
||||
return "<illegal nil package>"
|
||||
} else if len(pkg.Path) <= 1 {
|
||||
return fmt.Sprintf("package <illegal path %q>", pkg.Path)
|
||||
}
|
||||
// Omit head as all packages have the DefaultRootDocument prepended at parse time.
|
||||
path := make(Ref, len(pkg.Path)-1)
|
||||
path[0] = VarTerm(string(pkg.Path[1].Value.(String)))
|
||||
copy(path[1:], pkg.Path[2:])
|
||||
return fmt.Sprintf("package %v", path)
|
||||
buf, _ := pkg.AppendText(make([]byte, 0, pkg.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (pkg *Package) MarshalJSON() ([]byte, error) {
|
||||
@@ -637,11 +596,8 @@ func (imp *Import) Name() Var {
|
||||
}
|
||||
|
||||
func (imp *Import) String() string {
|
||||
buf := []string{"import", imp.Path.String()}
|
||||
if len(imp.Alias) > 0 {
|
||||
buf = append(buf, "as", imp.Alias.String())
|
||||
}
|
||||
return strings.Join(buf, " ")
|
||||
buf, _ := imp.AppendText(make([]byte, 0, imp.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (imp *Import) MarshalJSON() ([]byte, error) {
|
||||
@@ -752,11 +708,12 @@ func (rule *Rule) Ref() Ref {
|
||||
}
|
||||
|
||||
func (rule *Rule) String() string {
|
||||
regoVersion := DefaultRegoVersion
|
||||
opts := toStringOpts{}
|
||||
if rule.Module != nil {
|
||||
regoVersion = rule.Module.RegoVersion()
|
||||
opts.regoVersion = rule.Module.RegoVersion()
|
||||
}
|
||||
return rule.stringWithOpts(toStringOpts{regoVersion: regoVersion})
|
||||
buf, _ := rule.appendWithOpts(opts, make([]byte, 0, rule.stringLengthWithOpts(opts)))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
type toStringOpts struct {
|
||||
@@ -770,80 +727,46 @@ func (o toStringOpts) RegoVersion() RegoVersion {
|
||||
return o.regoVersion
|
||||
}
|
||||
|
||||
func (rule *Rule) stringWithOpts(opts toStringOpts) string {
|
||||
buf := []string{}
|
||||
if rule.Default {
|
||||
buf = append(buf, "default")
|
||||
}
|
||||
buf = append(buf, rule.Head.stringWithOpts(opts))
|
||||
if !rule.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, "if")
|
||||
}
|
||||
buf = append(buf, "{", rule.Body.String(), "}")
|
||||
}
|
||||
if rule.Else != nil {
|
||||
buf = append(buf, rule.Else.elseString(opts))
|
||||
}
|
||||
return strings.Join(buf, " ")
|
||||
}
|
||||
|
||||
func (rule *Rule) isFunction() bool {
|
||||
return len(rule.Head.Args) > 0
|
||||
}
|
||||
|
||||
// ruleJSON is used for JSON serialization of Rule to avoid map allocation overhead.
|
||||
// Field order is alphabetical to match previous map-based output.
|
||||
type ruleJSON struct {
|
||||
Annotations []*Annotations `json:"annotations,omitempty"`
|
||||
Body Body `json:"body"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
Else *Rule `json:"else,omitempty"`
|
||||
Head *Head `json:"head"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
func (rule *Rule) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]any{
|
||||
"head": rule.Head,
|
||||
"body": rule.Body,
|
||||
data := ruleJSON{
|
||||
Head: rule.Head,
|
||||
Body: rule.Body,
|
||||
}
|
||||
|
||||
if rule.Default {
|
||||
data["default"] = true
|
||||
data.Default = true
|
||||
}
|
||||
|
||||
if rule.Else != nil {
|
||||
data["else"] = rule.Else
|
||||
data.Else = rule.Else
|
||||
}
|
||||
|
||||
if astJSON.GetOptions().MarshalOptions.IncludeLocation.Rule {
|
||||
if rule.Location != nil {
|
||||
data["location"] = rule.Location
|
||||
}
|
||||
data.Location = rule.Location
|
||||
}
|
||||
|
||||
if len(rule.Annotations) != 0 {
|
||||
data["annotations"] = rule.Annotations
|
||||
data.Annotations = rule.Annotations
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func (rule *Rule) elseString(opts toStringOpts) string {
|
||||
var buf []string
|
||||
|
||||
buf = append(buf, "else")
|
||||
|
||||
value := rule.Head.Value
|
||||
if value != nil {
|
||||
buf = append(buf, "=", value.String())
|
||||
}
|
||||
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, "if")
|
||||
}
|
||||
|
||||
buf = append(buf, "{", rule.Body.String(), "}")
|
||||
|
||||
if rule.Else != nil {
|
||||
buf = append(buf, rule.Else.elseString(opts))
|
||||
}
|
||||
|
||||
return strings.Join(buf, " ")
|
||||
}
|
||||
|
||||
// NewHead returns a new Head object. If args are provided, the first will be
|
||||
// used for the key and the second will be used for the value.
|
||||
func NewHead(name Var, args ...*Term) *Head {
|
||||
@@ -1002,37 +925,8 @@ func (head *Head) String() string {
|
||||
}
|
||||
|
||||
func (head *Head) stringWithOpts(opts toStringOpts) string {
|
||||
buf := strings.Builder{}
|
||||
buf.WriteString(head.Ref().String())
|
||||
containsAdded := false
|
||||
|
||||
switch {
|
||||
case len(head.Args) != 0:
|
||||
buf.WriteString(head.Args.String())
|
||||
case len(head.Reference) == 1 && head.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
buf.WriteRune('[')
|
||||
buf.WriteString(head.Key.String())
|
||||
buf.WriteRune(']')
|
||||
default:
|
||||
containsAdded = true
|
||||
buf.WriteString(" contains ")
|
||||
buf.WriteString(head.Key.String())
|
||||
}
|
||||
}
|
||||
if head.Value != nil {
|
||||
if head.Assign {
|
||||
buf.WriteString(" := ")
|
||||
} else {
|
||||
buf.WriteString(" = ")
|
||||
}
|
||||
buf.WriteString(head.Value.String())
|
||||
} else if !containsAdded && head.Name == "" && head.Key != nil {
|
||||
buf.WriteString(" contains ")
|
||||
buf.WriteString(head.Key.String())
|
||||
}
|
||||
return buf.String()
|
||||
buf, _ := head.appendWithOpts(opts, make([]byte, 0, head.stringLengthWithOpts(opts)))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (head *Head) MarshalJSON() ([]byte, error) {
|
||||
@@ -1103,11 +997,8 @@ func (a Args) Copy() Args {
|
||||
}
|
||||
|
||||
func (a Args) String() string {
|
||||
buf := make([]string, 0, len(a))
|
||||
for _, t := range a {
|
||||
buf = append(buf, t.String())
|
||||
}
|
||||
return "(" + strings.Join(buf, ", ") + ")"
|
||||
buf, _ := a.AppendText(make([]byte, 0, a.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Loc returns the Location of a.
|
||||
@@ -1240,11 +1131,12 @@ func (body Body) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (body Body) String() string {
|
||||
buf := make([]string, 0, len(body))
|
||||
for _, v := range body {
|
||||
buf = append(buf, v.String())
|
||||
}
|
||||
return strings.Join(buf, "; ")
|
||||
buf, _ := body.AppendText(make([]byte, 0, body.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (body Body) AppendText(buf []byte) ([]byte, error) {
|
||||
return AppendDelimeted(buf, body, "; ")
|
||||
}
|
||||
|
||||
// Vars returns a VarSet containing variables in body. The params can be set to
|
||||
@@ -1555,50 +1447,41 @@ func (expr *Expr) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (expr *Expr) String() string {
|
||||
buf := make([]string, 0, 2+len(expr.With))
|
||||
if expr.Negated {
|
||||
buf = append(buf, "not")
|
||||
}
|
||||
switch t := expr.Terms.(type) {
|
||||
case []*Term:
|
||||
if expr.IsEquality() && validEqAssignArgCount(expr) {
|
||||
buf = append(buf, fmt.Sprintf("%v %v %v", t[1], Equality.Infix, t[2]))
|
||||
} else {
|
||||
buf = append(buf, Call(t).String())
|
||||
}
|
||||
case fmt.Stringer:
|
||||
buf = append(buf, t.String())
|
||||
}
|
||||
buf, _ := expr.AppendText(make([]byte, 0, expr.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
for i := range expr.With {
|
||||
buf = append(buf, expr.With[i].String())
|
||||
}
|
||||
|
||||
return strings.Join(buf, " ")
|
||||
// exprJSON is used for JSON serialization of Expr to avoid map allocation overhead.
|
||||
// Field order is alphabetical to match previous map-based output.
|
||||
type exprJSON struct {
|
||||
Generated bool `json:"generated,omitempty"`
|
||||
Index int `json:"index"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Negated bool `json:"negated,omitempty"`
|
||||
Terms any `json:"terms"`
|
||||
With []*With `json:"with,omitempty"`
|
||||
}
|
||||
|
||||
func (expr *Expr) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]any{
|
||||
"terms": expr.Terms,
|
||||
"index": expr.Index,
|
||||
data := exprJSON{
|
||||
Index: expr.Index,
|
||||
Terms: expr.Terms,
|
||||
}
|
||||
|
||||
if len(expr.With) > 0 {
|
||||
data["with"] = expr.With
|
||||
data.With = expr.With
|
||||
}
|
||||
|
||||
if expr.Generated {
|
||||
data["generated"] = true
|
||||
data.Generated = true
|
||||
}
|
||||
|
||||
if expr.Negated {
|
||||
data["negated"] = true
|
||||
data.Negated = true
|
||||
}
|
||||
|
||||
if astJSON.GetOptions().MarshalOptions.IncludeLocation.Expr {
|
||||
if expr.Location != nil {
|
||||
data["location"] = expr.Location
|
||||
}
|
||||
data.Location = expr.Location
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
@@ -1668,17 +1551,8 @@ func visitCogeneratedExprs(expr *Expr, f func(*Expr) bool) {
|
||||
}
|
||||
|
||||
func (d *SomeDecl) String() string {
|
||||
if call, ok := d.Symbols[0].Value.(Call); ok {
|
||||
if len(call) == 4 {
|
||||
return "some " + call[1].String() + ", " + call[2].String() + " in " + call[3].String()
|
||||
}
|
||||
return "some " + call[1].String() + " in " + call[2].String()
|
||||
}
|
||||
buf := make([]string, len(d.Symbols))
|
||||
for i := range buf {
|
||||
buf[i] = d.Symbols[i].String()
|
||||
}
|
||||
return "some " + strings.Join(buf, ", ")
|
||||
buf, _ := d.AppendText(make([]byte, 0, d.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// SetLoc sets the Location on d.
|
||||
@@ -1797,7 +1671,8 @@ func (q *Every) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (w *With) String() string {
|
||||
return "with " + w.Target.String() + " as " + w.Value.String()
|
||||
buf, _ := w.AppendText(make([]byte, 0, w.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Equal returns true if this With is equals the other With.
|
||||
@@ -1854,16 +1729,22 @@ func (w *With) SetLoc(loc *Location) {
|
||||
w.Location = loc
|
||||
}
|
||||
|
||||
// withJSON is used for JSON serialization of With to avoid map allocation overhead.
|
||||
// Field order is alphabetical to match previous map-based output.
|
||||
type withJSON struct {
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Target *Term `json:"target"`
|
||||
Value *Term `json:"value"`
|
||||
}
|
||||
|
||||
func (w *With) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]any{
|
||||
"target": w.Target,
|
||||
"value": w.Value,
|
||||
data := withJSON{
|
||||
Target: w.Target,
|
||||
Value: w.Value,
|
||||
}
|
||||
|
||||
if astJSON.GetOptions().MarshalOptions.IncludeLocation.With {
|
||||
if w.Location != nil {
|
||||
data["location"] = w.Location
|
||||
}
|
||||
data.Location = w.Location
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (m *Module) AppendText(buf []byte) ([]byte, error) {
|
||||
if m == nil {
|
||||
return append(buf, "<nil module>"...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// NOTE(anderseknert): this DOES allocate still, and while that's unfortunate,
|
||||
// we'll be better off dealing with that when we have v2 JSON in the stdlib than
|
||||
// doing manual JSON marshalling (and string length calculations) here.
|
||||
for _, annotations := range m.Annotations {
|
||||
// rule annotations are attached to rules, so only check for package scoped ones here
|
||||
if annotations.Scope == "package" || annotations.Scope == "subpackages" {
|
||||
buf = append(buf, "# METADATA\n# "...)
|
||||
buf = append(buf, annotations.String()...)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
if buf, err = m.Package.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
|
||||
if len(m.Imports) > 0 {
|
||||
for _, imp := range m.Imports {
|
||||
buf = append(buf, '\n')
|
||||
if buf, err = imp.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
if len(m.Rules) > 0 {
|
||||
for _, rule := range m.Rules {
|
||||
buf = append(buf, '\n')
|
||||
if buf, err = rule.appendWithOpts(toStringOpts{regoVersion: m.regoVersion}, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
if pkg == nil {
|
||||
return append(buf, "<illegal nil package>"...), nil
|
||||
}
|
||||
if len(pkg.Path) <= 1 {
|
||||
buf = append(buf, "package <illegal path \""...)
|
||||
if buf, err = pkg.Path.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, "\">"...), nil
|
||||
}
|
||||
|
||||
buf = append(buf, "package "...)
|
||||
|
||||
path := pkg.Path[1:] // omit "data"
|
||||
|
||||
if s, ok := path[0].Value.(String); ok {
|
||||
buf = append(buf, s...) // first term should never be quoted
|
||||
if len(path) == 1 {
|
||||
return buf, nil
|
||||
}
|
||||
buf = append(buf, '.')
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
return path.AppendText(buf)
|
||||
}
|
||||
|
||||
func (imp *Import) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "import "...)
|
||||
var err error
|
||||
if buf, err = imp.Path.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if imp.Alias != "" {
|
||||
buf = append(buf, ' ', 'a', 's', ' ')
|
||||
buf = append(buf, imp.Alias...)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Rule) AppendText(buf []byte) ([]byte, error) {
|
||||
regoVersion := DefaultRegoVersion
|
||||
if r.Module != nil {
|
||||
regoVersion = r.Module.RegoVersion()
|
||||
}
|
||||
return r.appendWithOpts(toStringOpts{regoVersion: regoVersion}, buf)
|
||||
}
|
||||
|
||||
func (r *Rule) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
// See note in [Module.AppendText] regarding annotations.
|
||||
for _, annotations := range r.Annotations {
|
||||
buf = append(buf, "# METADATA\n# "...)
|
||||
buf = append(buf, annotations.String()...)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
if r.Default {
|
||||
buf = append(buf, "default "...)
|
||||
}
|
||||
|
||||
var err error
|
||||
if buf, err = r.Head.appendWithOpts(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !r.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, " if { "...)
|
||||
default:
|
||||
buf = append(buf, " { "...)
|
||||
}
|
||||
if buf, err = r.Body.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " }"...)
|
||||
}
|
||||
if r.Else != nil {
|
||||
if buf, err = r.Else.appendElse(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
buf = append(buf, " else "...)
|
||||
|
||||
var err error
|
||||
if r.Head.Value != nil {
|
||||
buf = append(buf, "= "...)
|
||||
if buf, err = r.Head.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v := opts.RegoVersion(); v == RegoV1 || v == RegoV0CompatV1 {
|
||||
buf = append(buf, " if { "...)
|
||||
} else {
|
||||
buf = append(buf, " { "...)
|
||||
}
|
||||
if buf, err = r.Body.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " }"...)
|
||||
|
||||
if r.Else != nil {
|
||||
if buf, err = r.Else.appendElse(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (h *Head) AppendText(buf []byte) ([]byte, error) {
|
||||
return h.appendWithOpts(toStringOpts{}, buf)
|
||||
}
|
||||
|
||||
func (h *Head) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
if h.Reference == nil {
|
||||
buf = append(buf, h.Name...)
|
||||
} else {
|
||||
if buf, err = h.Reference.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
containsAdded := false
|
||||
switch {
|
||||
case len(h.Args) != 0:
|
||||
if buf, err = h.Args.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case len(h.Reference) == 1 && h.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
buf = append(buf, '[')
|
||||
if buf, err = h.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
default:
|
||||
if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containsAdded = true
|
||||
}
|
||||
}
|
||||
if h.Value != nil {
|
||||
if h.Assign {
|
||||
buf = append(buf, " := "...)
|
||||
} else {
|
||||
buf = append(buf, " = "...)
|
||||
}
|
||||
if buf, err = h.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !containsAdded && h.Name == "" && h.Key != nil {
|
||||
if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (a Args) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf = append(buf, '(')
|
||||
if buf, err = AppendDelimeted(buf, a, ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ')'), nil
|
||||
}
|
||||
|
||||
func (expr *Expr) AppendText(buf []byte) ([]byte, error) {
|
||||
if expr.Negated {
|
||||
buf = append(buf, "not "...)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch t := expr.Terms.(type) {
|
||||
case []*Term:
|
||||
if expr.IsEquality() && validEqAssignArgCount(expr) {
|
||||
if buf, err = t[1].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(append(append(buf, ' '), Equality.Infix...), ' ')
|
||||
if buf, err = t[2].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if buf, err = Call(t).AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case encoding.TextAppender:
|
||||
if buf, err = t.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported expr terms type: %T", expr.Terms)
|
||||
}
|
||||
|
||||
if len(expr.With) > 0 {
|
||||
buf = append(buf, ' ')
|
||||
}
|
||||
|
||||
return AppendDelimeted(buf, expr.With, " ")
|
||||
}
|
||||
|
||||
func (w *With) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "with "...)
|
||||
var err error
|
||||
if buf, err = w.Target.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " as "...)
|
||||
if buf, err = w.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (w *Every) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "every "...)
|
||||
var err error
|
||||
if w.Key != nil {
|
||||
if buf, err = w.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
if buf, err = w.Value.AppendText(buf); err == nil {
|
||||
buf = append(buf, " in "...)
|
||||
if buf, err = w.Domain.AppendText(buf); err == nil {
|
||||
buf = append(buf, " { "...)
|
||||
if buf, err = w.Body.AppendText(buf); err == nil {
|
||||
buf = append(buf, " }"...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (d *SomeDecl) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf = append(buf, "some "...)
|
||||
if call, ok := d.Symbols[0].Value.(Call); ok {
|
||||
if buf, err = call[1].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(call) == 3 {
|
||||
buf = append(buf, " in "...)
|
||||
} else {
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
if buf, err = call[2].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(call) == 4 {
|
||||
buf = append(buf, " in "...)
|
||||
if buf, err = call[3].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
buf, err = AppendDelimeted(buf, d.Symbols, ", ")
|
||||
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (c *Comment) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(append(buf, '#'), c.Text...), nil
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright 2026 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ast
|
||||
|
||||
// CountFunc counts the number of items in a slice S that satisfy predicate function f.
|
||||
func CountFunc[T any, S ~[]T](items S, f func(T) bool) (n int) {
|
||||
for i := range items {
|
||||
if f(items[i]) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// StringLengther is an interface for types that can report their string length without
|
||||
// actually constructing the string. This is useful for pre-allocating buffers, like those
|
||||
// used in AppendText, strings.Builder, bytes.Buffer, etc.
|
||||
type StringLengther interface {
|
||||
StringLength() int
|
||||
}
|
||||
|
||||
// TermSliceStringLength returns the total string length of the given terms, as reported
|
||||
// by the [StringLengther.StringLength] method implementation of each term's [Value]. The
|
||||
// delimLen value will be added between each term's length to account for a delimiter, or
|
||||
// no delimiter if delimLen is 0.
|
||||
// Implementation note: this function is optimized for inlining, and just meets the threshold
|
||||
// for that. Don't change without making sure that's still the case.
|
||||
func TermSliceStringLength(terms []*Term, delimLen int) (n int) {
|
||||
for i := range terms {
|
||||
n += terms[i].StringLength() + delimLen
|
||||
}
|
||||
return max(n-delimLen, 0)
|
||||
}
|
||||
|
||||
func (t *Term) StringLength() int {
|
||||
if sl, ok := t.Value.(StringLengther); ok {
|
||||
return sl.StringLength()
|
||||
}
|
||||
|
||||
panic("expected all ast.Value types to implement StringLenghter interface, got: " + ValueName(t.Value))
|
||||
}
|
||||
|
||||
func (s String) StringLength() int {
|
||||
n := 2 // surrounding quotes
|
||||
bs := util.StringToByteSlice(s)
|
||||
for i := 0; i < len(bs); {
|
||||
r, size := utf8.DecodeRune(bs[i:])
|
||||
switch r {
|
||||
case '\\', '"':
|
||||
n += 2 // escaped backslash or quote
|
||||
case '\b', '\f', '\n', '\r', '\t':
|
||||
n += 2 // escaped control characters
|
||||
default:
|
||||
if r < 0x20 {
|
||||
n += 6 // unicode escape for other control characters
|
||||
} else {
|
||||
n += size // normal rune
|
||||
}
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n Number) StringLength() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (b Boolean) StringLength() int {
|
||||
if b {
|
||||
return 4
|
||||
}
|
||||
return 5
|
||||
}
|
||||
|
||||
func (Null) StringLength() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (s *set) StringLength() int {
|
||||
if s.Len() == 0 {
|
||||
return 5 // set()
|
||||
}
|
||||
// surrounding {} + ", " for every element - 1
|
||||
return TermSliceStringLength(s.Slice(), 2) + 2
|
||||
}
|
||||
|
||||
func (a *Array) StringLength() int {
|
||||
if a.Len() == 0 {
|
||||
return 2 // []
|
||||
}
|
||||
// surrounding brackets + ", " for every element - 1
|
||||
return TermSliceStringLength(a.elems, 2) + 2
|
||||
}
|
||||
|
||||
func (o *object) StringLength() (n int) {
|
||||
if o.Len() == 0 {
|
||||
return 2 // {}
|
||||
}
|
||||
// ": " for every item + ", " for every item - 1
|
||||
o.Foreach(func(key, value *Term) {
|
||||
n += key.StringLength() + 4 + value.StringLength() // ": " and ", "
|
||||
})
|
||||
return n // surrounding {} but also minus last ", "
|
||||
}
|
||||
|
||||
func (ts *TemplateString) StringLength() (n int) {
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
n += 2 + x.StringLength() // for {}
|
||||
case *Term:
|
||||
if s, ok := x.Value.(String); ok {
|
||||
n += len(s) + countUnescapedLeftCurly(string(s))
|
||||
} else {
|
||||
n += x.StringLength()
|
||||
}
|
||||
default:
|
||||
n += 9 // <invalid>
|
||||
}
|
||||
}
|
||||
return n + 3 // $"" or $``
|
||||
}
|
||||
|
||||
func (c Call) StringLength() int {
|
||||
return c[0].StringLength() + 2 + TermSliceStringLength(c[1:], 2)
|
||||
}
|
||||
|
||||
func (r Ref) StringLength() (n int) {
|
||||
rlen := len(r)
|
||||
if rlen == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
n = len(s) // first term should never be quoted
|
||||
} else {
|
||||
n = r[0].StringLength()
|
||||
}
|
||||
|
||||
if rlen == 1 {
|
||||
return n
|
||||
}
|
||||
|
||||
for _, p := range r[1:] {
|
||||
switch v := p.Value.(type) {
|
||||
case String:
|
||||
str := string(v)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
n += 1 + len(str) // dot + name
|
||||
} else {
|
||||
n += 2 + p.StringLength() // brackets
|
||||
}
|
||||
default:
|
||||
n += 2 + p.StringLength() // brackets
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (v Var) StringLength() int {
|
||||
if v.IsWildcard() {
|
||||
return 1
|
||||
}
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (s *SetComprehension) StringLength() int {
|
||||
return s.Term.StringLength() + s.Body.StringLength() + 5 // {} and " | "
|
||||
}
|
||||
|
||||
func (a *ArrayComprehension) StringLength() int {
|
||||
return a.Term.StringLength() + a.Body.StringLength() + 5 // [] and " | "
|
||||
}
|
||||
|
||||
func (o *ObjectComprehension) StringLength() (n int) {
|
||||
n += o.Key.StringLength()
|
||||
n += o.Value.StringLength()
|
||||
n += o.Body.StringLength()
|
||||
return n + 7 // "{}"", " | ", and ": "
|
||||
}
|
||||
|
||||
func (m *Module) StringLength() (n int) {
|
||||
if m.Package != nil {
|
||||
n += m.Package.StringLength() + 2 // newlines
|
||||
}
|
||||
|
||||
if len(m.Imports) > 0 {
|
||||
for _, imp := range m.Imports {
|
||||
n += imp.StringLength() + 1 // newline
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.Rules) > 0 {
|
||||
for _, rule := range m.Rules {
|
||||
n += rule.stringLengthWithOpts(toStringOpts{regoVersion: m.regoVersion}) + 1 // newline
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *Package) StringLength() int {
|
||||
if p == nil {
|
||||
return 21 // <illegal nil package>
|
||||
}
|
||||
if len(p.Path) <= 1 {
|
||||
return 25 + p.Path.StringLength() // // package <illegal path " ... ">
|
||||
}
|
||||
|
||||
return 8 + p.Path[1:].StringLength() // "package ..."
|
||||
}
|
||||
|
||||
func (i *Import) StringLength() (n int) {
|
||||
n = 7 + i.Path.StringLength() // "import " and path
|
||||
if i.Alias != "" {
|
||||
n += 4 + i.Alias.StringLength() // " as " and alias
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *Rule) StringLength() int {
|
||||
return r.stringLengthWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
func (r *Rule) stringLengthWithOpts(opts toStringOpts) int {
|
||||
n := 0
|
||||
if r.Default {
|
||||
n += 8 // "default "
|
||||
}
|
||||
n += r.Head.stringLengthWithOpts(opts)
|
||||
if !r.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
n += 6 // " if { "
|
||||
default:
|
||||
n += 3 // " { "
|
||||
}
|
||||
n += r.Body.StringLength() + 2 // body and closing " }"
|
||||
}
|
||||
if r.Else != nil {
|
||||
n += r.Else.stringLengthWithOpts(opts)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (h *Head) StringLength() int {
|
||||
return h.stringLengthWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
func (h *Head) stringLengthWithOpts(opts toStringOpts) int {
|
||||
n := h.Reference.StringLength()
|
||||
containsAdded := false
|
||||
switch {
|
||||
case len(h.Args) != 0:
|
||||
n += h.Args.StringLength()
|
||||
case len(h.Reference) == 1 && h.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
n += 2 + h.Key.StringLength() // for []
|
||||
default:
|
||||
n += 10 + h.Key.StringLength() // " contains "
|
||||
containsAdded = true
|
||||
}
|
||||
}
|
||||
if h.Value != nil {
|
||||
if h.Assign {
|
||||
n += 4 // " := "
|
||||
} else {
|
||||
n += 3 // " = "
|
||||
}
|
||||
n += h.Value.StringLength()
|
||||
} else if !containsAdded && h.Name == "" && h.Key != nil {
|
||||
n += 10 + h.Key.StringLength() // " contains "
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (a Args) StringLength() (n int) {
|
||||
n = 2 // ()
|
||||
for _, t := range a {
|
||||
n += t.StringLength() + 2 // ", "
|
||||
}
|
||||
return n - 2 // minus last ", "
|
||||
}
|
||||
|
||||
func (b Body) StringLength() (n int) {
|
||||
for _, expr := range b {
|
||||
n += expr.StringLength() + 2 // "; "
|
||||
}
|
||||
return max(n-2, 0) // minus last "; " (if `n` isn't 0)
|
||||
}
|
||||
|
||||
func (e *Expr) StringLength() (n int) {
|
||||
if e.Negated {
|
||||
n += 4 // "not "
|
||||
}
|
||||
switch terms := e.Terms.(type) {
|
||||
case []*Term:
|
||||
if e.IsEquality() && validEqAssignArgCount(e) {
|
||||
n += terms[1].StringLength() + len(Equality.Infix) + terms[2].StringLength() + 2 // spaces around =
|
||||
} else {
|
||||
n += Call(terms).StringLength()
|
||||
}
|
||||
case StringLengther:
|
||||
n += terms.StringLength()
|
||||
default:
|
||||
panic(fmt.Sprintf("string length estimation not implemented for type: %T", e.Terms))
|
||||
}
|
||||
|
||||
for _, w := range e.With {
|
||||
n += w.StringLength() + 1 // space before with
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (w *With) StringLength() int {
|
||||
return w.Target.StringLength() + w.Value.StringLength() + 9 // "with " and " as "
|
||||
}
|
||||
|
||||
func (e *Every) StringLength() int {
|
||||
n := 6 // "every "
|
||||
if e.Key != nil {
|
||||
n += e.Key.StringLength() + 2 // ", "
|
||||
}
|
||||
n += e.Value.StringLength() + 4 // " in "
|
||||
n += e.Domain.StringLength() + 3 // " { "
|
||||
n += e.Body.StringLength() + 2 // " }"
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *SomeDecl) StringLength() int {
|
||||
n := 5 // "some "
|
||||
if call, ok := s.Symbols[0].Value.(Call); ok {
|
||||
n += 4 // " in "
|
||||
n += call[1].StringLength()
|
||||
if len(call) == 4 {
|
||||
n += 2 // ", "
|
||||
}
|
||||
n += call[2].StringLength()
|
||||
if len(call) == 4 {
|
||||
n += call[3].StringLength()
|
||||
}
|
||||
return n
|
||||
}
|
||||
return n + TermSliceStringLength(s.Symbols, 2)
|
||||
}
|
||||
|
||||
func (c *Comment) StringLength() int {
|
||||
return 1 + len(c.Text) // '#' + text
|
||||
}
|
||||
+3
-22
@@ -2,7 +2,6 @@ package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
@@ -12,15 +11,7 @@ var (
|
||||
TermPtrPool = util.NewSyncPool[Term]()
|
||||
BytesReaderPool = util.NewSyncPool[bytes.Reader]()
|
||||
IndexResultPool = util.NewSyncPool[IndexResult]()
|
||||
bbPool = util.NewSyncPool[bytes.Buffer]()
|
||||
// Needs custom pool because of custom Put logic.
|
||||
sbPool = &stringBuilderPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return &strings.Builder{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Needs custom pool because of custom Put logic.
|
||||
varVisitorPool = &vvPool{
|
||||
pool: sync.Pool{
|
||||
@@ -31,18 +22,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
stringBuilderPool struct{ pool sync.Pool }
|
||||
vvPool struct{ pool sync.Pool }
|
||||
)
|
||||
|
||||
func (p *stringBuilderPool) Get() *strings.Builder {
|
||||
return p.pool.Get().(*strings.Builder)
|
||||
}
|
||||
|
||||
func (p *stringBuilderPool) Put(sb *strings.Builder) {
|
||||
sb.Reset()
|
||||
p.pool.Put(sb)
|
||||
type vvPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (p *vvPool) Get() *VarVisitor {
|
||||
|
||||
+119
-163
@@ -12,7 +12,6 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -29,8 +28,6 @@ var (
|
||||
NullValue Value = Null{}
|
||||
|
||||
errFindNotFound = errors.New("find: not found")
|
||||
|
||||
varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
|
||||
)
|
||||
|
||||
// Location records a position in source code.
|
||||
@@ -413,19 +410,24 @@ func (term *Term) IsGround() bool {
|
||||
return term.Value.IsGround()
|
||||
}
|
||||
|
||||
// termJSON is used to serialize Term to JSON without map allocation.
|
||||
type termJSON struct {
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Value Value `json:"value"`
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the term.
|
||||
//
|
||||
// Specialized marshalling logic is required to include a type hint for Value.
|
||||
func (term *Term) MarshalJSON() ([]byte, error) {
|
||||
d := map[string]any{
|
||||
"type": ValueName(term.Value),
|
||||
"value": term.Value,
|
||||
d := termJSON{
|
||||
Type: ValueName(term.Value),
|
||||
Value: term.Value,
|
||||
}
|
||||
jsonOptions := astJSON.GetOptions().MarshalOptions
|
||||
if jsonOptions.IncludeLocation.Term {
|
||||
if term.Location != nil {
|
||||
d["location"] = term.Location
|
||||
}
|
||||
d.Location = term.Location
|
||||
}
|
||||
return json.Marshal(d)
|
||||
}
|
||||
@@ -925,30 +927,8 @@ func (*TemplateString) IsGround() bool {
|
||||
}
|
||||
|
||||
func (ts *TemplateString) String() string {
|
||||
str := strings.Builder{}
|
||||
str.WriteString("$\"")
|
||||
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
str.WriteByte('{')
|
||||
str.WriteString(p.String())
|
||||
str.WriteByte('}')
|
||||
case *Term:
|
||||
s := p.String()
|
||||
if _, ok := x.Value.(String); ok {
|
||||
s = strings.TrimPrefix(s, "\"")
|
||||
s = strings.TrimSuffix(s, "\"")
|
||||
s = EscapeTemplateStringStringPart(s)
|
||||
}
|
||||
str.WriteString(s)
|
||||
default:
|
||||
str.WriteString("<invalid>")
|
||||
}
|
||||
}
|
||||
|
||||
str.WriteByte('"')
|
||||
return str.String()
|
||||
buf, _ := ts.AppendText(make([]byte, 0, ts.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func TemplateStringTerm(multiLine bool, parts ...Node) *Term {
|
||||
@@ -973,23 +953,25 @@ func EscapeTemplateStringStringPart(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
l := len(s)
|
||||
escaped := make([]byte, 0, l+numUnescaped)
|
||||
return util.ByteSliceToString(AppendEscapedTemplateStringStringPart(make([]byte, 0, len(s)+numUnescaped), s))
|
||||
}
|
||||
|
||||
func AppendEscapedTemplateStringStringPart(buf []byte, s string) []byte {
|
||||
if s[0] == '{' {
|
||||
escaped = append(escaped, '\\', s[0])
|
||||
buf = append(buf, '\\', s[0])
|
||||
} else {
|
||||
escaped = append(escaped, s[0])
|
||||
buf = append(buf, s[0])
|
||||
}
|
||||
|
||||
for i := 1; i < l; i++ {
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] == '{' && s[i-1] != '\\' {
|
||||
escaped = append(escaped, '\\', s[i])
|
||||
buf = append(buf, '\\', s[i])
|
||||
} else {
|
||||
escaped = append(escaped, s[i])
|
||||
buf = append(buf, s[i])
|
||||
}
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(escaped)
|
||||
return buf
|
||||
}
|
||||
|
||||
func countUnescapedLeftCurly(s string) (n int) {
|
||||
@@ -1340,66 +1322,60 @@ func (ref Ref) Ptr() (string, error) {
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// IsVarCompatibleString returns true if s is a valid variable name. String s is a valid variable
|
||||
// name if it starts with a letter (a-z or A-Z) or underscore (_) and is followed by
|
||||
// letters (a-z or A-Z), digits (0-9), and underscores.
|
||||
func IsVarCompatibleString(s string) bool {
|
||||
return varRegexp.MatchString(s)
|
||||
l := len(s)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
// not exactly easy on the eyes, but often orders of magnitude faster
|
||||
// than using a compiled regex (see benchmarks in term_bench_test.go)
|
||||
is_letter := func(c byte) bool {
|
||||
return (c > 96 && c < 123) || (c > 64 && c < 91)
|
||||
}
|
||||
is_digit := func(c byte) bool {
|
||||
return c > 47 && c < 58
|
||||
}
|
||||
|
||||
// first character must be a letter or underscore
|
||||
c := s[0]
|
||||
if !(is_letter(c) || c == 95) {
|
||||
return false
|
||||
}
|
||||
|
||||
// remaining characters must be letters, digits, or underscores
|
||||
for i := 1; i < l; i++ {
|
||||
if c = s[i]; !(is_letter(c) || is_digit(c) || c == 95) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ref Ref) String() string {
|
||||
// Note(anderseknert):
|
||||
// Options tried in the order of cheapness, where after some effort,
|
||||
// only the last option now requires a (single) allocation:
|
||||
// 1. empty ref
|
||||
// 2. single var ref
|
||||
// 3. built-in function ref
|
||||
// 4. concatenated parts
|
||||
reflen := len(ref)
|
||||
if reflen == 0 {
|
||||
l := len(ref)
|
||||
// First check for zero-alloc options, as making the buffer for AppendText
|
||||
// always costs an allocation.
|
||||
if l == 0 {
|
||||
return ""
|
||||
}
|
||||
if reflen == 1 {
|
||||
if l == 1 {
|
||||
if s, ok := ref[0].Value.(String); ok {
|
||||
// Ref head should normally be a Var, but if for some reason
|
||||
// it's a string, don't quote it.
|
||||
return string(s)
|
||||
}
|
||||
return ref[0].Value.String()
|
||||
}
|
||||
if name, ok := BuiltinNameFromRef(ref); ok {
|
||||
return name
|
||||
}
|
||||
|
||||
_var := ref[0].Value.String()
|
||||
|
||||
bb := bbPool.Get()
|
||||
bb.Reset()
|
||||
|
||||
defer bbPool.Put(bb)
|
||||
|
||||
bb.Grow(len(_var) + len(ref[1:])*7) // rough estimate
|
||||
bb.WriteString(_var)
|
||||
|
||||
for _, p := range ref[1:] {
|
||||
switch p := p.Value.(type) {
|
||||
case String:
|
||||
str := string(p)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
bb.WriteByte('.')
|
||||
bb.WriteString(str)
|
||||
} else {
|
||||
bb.WriteByte('[')
|
||||
// Determine whether we need the full JSON-escaped form
|
||||
if strings.ContainsFunc(str, isControlOrBackslash) {
|
||||
bb.Write(strconv.AppendQuote(bb.AvailableBuffer(), str))
|
||||
} else {
|
||||
bb.WriteByte('"')
|
||||
bb.WriteString(str)
|
||||
bb.WriteByte('"')
|
||||
}
|
||||
bb.WriteByte(']')
|
||||
}
|
||||
default:
|
||||
bb.WriteByte('[')
|
||||
bb.WriteString(p.String())
|
||||
bb.WriteByte(']')
|
||||
}
|
||||
}
|
||||
|
||||
return bb.String()
|
||||
buf, _ := ref.AppendText(make([]byte, 0, ref.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// OutputVars returns a VarSet containing variables that would be bound by evaluating
|
||||
@@ -1442,6 +1418,15 @@ func NewArray(a ...*Term) *Array {
|
||||
return arr
|
||||
}
|
||||
|
||||
// NewArrayWithCapacity returns a new empty Array with the given capacity pre-allocated.
|
||||
func NewArrayWithCapacity(capacity int) *Array {
|
||||
return &Array{
|
||||
elems: make([]*Term, 0, capacity),
|
||||
hashs: make([]int, 0, capacity),
|
||||
ground: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Array represents an array as defined by the language. Arrays are similar to the
|
||||
// same types as defined by JSON with the exception that they can contain Vars
|
||||
// and References.
|
||||
@@ -1570,21 +1555,8 @@ func (arr *Array) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (arr *Array) String() string {
|
||||
sb := sbPool.Get()
|
||||
sb.Grow(len(arr.elems) * 16)
|
||||
|
||||
defer sbPool.Put(sb)
|
||||
|
||||
sb.WriteByte('[')
|
||||
for i, e := range arr.elems {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(e.String())
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
|
||||
return sb.String()
|
||||
buf, _ := arr.AppendText(make([]byte, 0, arr.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the array.
|
||||
@@ -1702,6 +1674,11 @@ func NewSet(t ...*Term) Set {
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSetWithCapacity returns a new empty Set with the given capacity pre-allocated.
|
||||
func NewSetWithCapacity(capacity int) Set {
|
||||
return newset(capacity)
|
||||
}
|
||||
|
||||
func newset(n int) *set {
|
||||
var keys []*Term
|
||||
if n > 0 {
|
||||
@@ -1765,25 +1742,8 @@ func (s *set) Hash() int {
|
||||
}
|
||||
|
||||
func (s *set) String() string {
|
||||
if s.Len() == 0 {
|
||||
return "set()"
|
||||
}
|
||||
|
||||
sb := sbPool.Get()
|
||||
sb.Grow(s.Len() * 16)
|
||||
|
||||
defer sbPool.Put(sb)
|
||||
|
||||
sb.WriteByte('{')
|
||||
for i := range s.sortedKeys() {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(s.keys[i].Value.String())
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
|
||||
return sb.String()
|
||||
buf, _ := s.AppendText(make([]byte, 0, s.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (s *set) sortedKeys() []*Term {
|
||||
@@ -1824,14 +1784,14 @@ func (s *set) Diff(other Set) Set {
|
||||
return NewSet()
|
||||
}
|
||||
|
||||
terms := make([]*Term, 0, len(s.keys))
|
||||
for _, term := range s.sortedKeys() {
|
||||
result := newset(len(s.keys))
|
||||
for _, term := range s.keys {
|
||||
if !other.Contains(term) {
|
||||
terms = append(terms, term)
|
||||
result.insert(term, false)
|
||||
}
|
||||
}
|
||||
|
||||
return NewSet(terms...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Intersect returns the set containing elements in both s and other.
|
||||
@@ -1846,21 +1806,28 @@ func (s *set) Intersect(other Set) Set {
|
||||
n = m
|
||||
}
|
||||
|
||||
terms := make([]*Term, 0, n)
|
||||
for _, term := range ss.sortedKeys() {
|
||||
result := newset(n)
|
||||
for _, term := range ss.keys {
|
||||
if so.Contains(term) {
|
||||
terms = append(terms, term)
|
||||
result.insert(term, false)
|
||||
}
|
||||
}
|
||||
|
||||
return NewSet(terms...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Union returns the set containing all elements of s and other.
|
||||
func (s *set) Union(other Set) Set {
|
||||
r := NewSet()
|
||||
s.Foreach(r.Add)
|
||||
other.Foreach(r.Add)
|
||||
o := other.(*set)
|
||||
// Pre-allocate with max size - avoids over-allocation for overlapping sets
|
||||
// while only requiring one potential grow for disjoint sets.
|
||||
r := newset(max(len(s.keys), len(o.keys)))
|
||||
for _, term := range s.keys {
|
||||
r.insert(term, false)
|
||||
}
|
||||
for _, term := range o.keys {
|
||||
r.insert(term, false)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -2034,6 +2001,11 @@ func NewObject(t ...[2]*Term) Object {
|
||||
return obj
|
||||
}
|
||||
|
||||
// NewObjectWithCapacity returns a new empty Object with the given capacity pre-allocated.
|
||||
func NewObjectWithCapacity(capacity int) Object {
|
||||
return newobject(capacity)
|
||||
}
|
||||
|
||||
// ObjectTerm creates a new Term with an Object value.
|
||||
func ObjectTerm(o ...[2]*Term) *Term {
|
||||
return &Term{Value: NewObject(o...)}
|
||||
@@ -2554,24 +2526,8 @@ func (obj *object) Len() int {
|
||||
}
|
||||
|
||||
func (obj *object) String() string {
|
||||
sb := sbPool.Get()
|
||||
sb.Grow(obj.Len() * 32)
|
||||
|
||||
defer sbPool.Put(sb)
|
||||
|
||||
sb.WriteByte('{')
|
||||
|
||||
for i, elem := range obj.sortedKeys() {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(elem.key.String())
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(elem.value.String())
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
|
||||
return sb.String()
|
||||
buf, _ := obj.AppendText(make([]byte, 0, obj.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (*object) get(*Term) *objectElem {
|
||||
@@ -2642,7 +2598,7 @@ func filterObject(o Value, filter Value) (Value, error) {
|
||||
case String, Number, Boolean, Null:
|
||||
return o, nil
|
||||
case *Array:
|
||||
values := NewArray()
|
||||
values := make([]*Term, 0, v.Len())
|
||||
for i := range v.Len() {
|
||||
subFilter := filteredObj.Get(InternedIntegerString(i))
|
||||
if subFilter != nil {
|
||||
@@ -2650,10 +2606,10 @@ func filterObject(o Value, filter Value) (Value, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = values.Append(NewTerm(filteredValue))
|
||||
values = append(values, NewTerm(filteredValue))
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
return NewArray(values...), nil
|
||||
case Set:
|
||||
terms := make([]*Term, 0, v.Len())
|
||||
for _, t := range v.Slice() {
|
||||
@@ -2776,7 +2732,8 @@ func (ac *ArrayComprehension) IsGround() bool {
|
||||
}
|
||||
|
||||
func (ac *ArrayComprehension) String() string {
|
||||
return "[" + ac.Term.String() + " | " + ac.Body.String() + "]"
|
||||
buf, _ := ac.AppendText(make([]byte, 0, ac.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// ObjectComprehension represents an object comprehension as defined in the language.
|
||||
@@ -2836,7 +2793,8 @@ func (oc *ObjectComprehension) IsGround() bool {
|
||||
}
|
||||
|
||||
func (oc *ObjectComprehension) String() string {
|
||||
return "{" + oc.Key.String() + ": " + oc.Value.String() + " | " + oc.Body.String() + "}"
|
||||
buf, _ := oc.AppendText(make([]byte, 0, oc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// SetComprehension represents a set comprehension as defined in the language.
|
||||
@@ -2893,7 +2851,8 @@ func (sc *SetComprehension) IsGround() bool {
|
||||
}
|
||||
|
||||
func (sc *SetComprehension) String() string {
|
||||
return "{" + sc.Term.String() + " | " + sc.Body.String() + "}"
|
||||
buf, _ := sc.AppendText(make([]byte, 0, sc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Call represents as function call in the language.
|
||||
@@ -2954,11 +2913,8 @@ func (c Call) Operands() []*Term {
|
||||
}
|
||||
|
||||
func (c Call) String() string {
|
||||
args := make([]string, len(c)-1)
|
||||
for i := 1; i < len(c); i++ {
|
||||
args[i-1] = c[i].String()
|
||||
}
|
||||
return fmt.Sprintf("%v(%v)", c[0], strings.Join(args, ", "))
|
||||
buf, _ := c.AppendText(make([]byte, 0, c.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func termSliceCopy(a []*Term) []*Term {
|
||||
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// AppendText appends the text representation of term (i.e. as printed in policy) to
|
||||
// buf and returns the extended buffer.
|
||||
func (term *Term) AppendText(buf []byte) ([]byte, error) {
|
||||
if app, ok := term.Value.(encoding.TextAppender); ok {
|
||||
return app.AppendText(buf)
|
||||
}
|
||||
|
||||
return append(buf, term.Value.String()...), nil
|
||||
}
|
||||
|
||||
func (v Var) AppendText(buf []byte) ([]byte, error) {
|
||||
if v.IsWildcard() {
|
||||
return append(buf, WildcardString...), nil
|
||||
}
|
||||
return append(buf, v...), nil
|
||||
}
|
||||
|
||||
func (b Boolean) AppendText(buf []byte) ([]byte, error) {
|
||||
if b {
|
||||
return append(buf, "true"...), nil
|
||||
}
|
||||
return append(buf, "false"...), nil
|
||||
}
|
||||
|
||||
func (Null) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, "null"...), nil
|
||||
}
|
||||
|
||||
func (str String) AppendText(buf []byte) ([]byte, error) {
|
||||
return strconv.AppendQuote(buf, string(str)), nil
|
||||
}
|
||||
|
||||
func (str String) appendNoQuote(buf []byte) []byte {
|
||||
// Append using strconv.AppendQuote for proper escaping, but trim off
|
||||
// the leading and trailing quotes afterwards.
|
||||
oldLen := len(buf)
|
||||
buf = strconv.AppendQuote(buf, string(str))
|
||||
newLen := len(buf)
|
||||
quoted := buf[oldLen:newLen]
|
||||
|
||||
return append(buf[:oldLen], quoted[1:len(quoted)-1]...)
|
||||
}
|
||||
|
||||
func (num Number) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, num...), nil
|
||||
}
|
||||
|
||||
func (arr *Array) AppendText(buf []byte) ([]byte, error) {
|
||||
buf, err := AppendDelimeted(append(buf, '['), arr.elems, ", ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ']'), nil
|
||||
}
|
||||
|
||||
func (obj *object) AppendText(buf []byte) ([]byte, error) {
|
||||
olen := obj.Len()
|
||||
if olen == 0 {
|
||||
return append(buf, "{}"...), nil
|
||||
}
|
||||
|
||||
buf = append(buf, '{')
|
||||
|
||||
var err error
|
||||
|
||||
// first key-value pair
|
||||
keys := obj.sortedKeys()
|
||||
for i := range keys {
|
||||
if buf, err = keys[i].key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ": "...)
|
||||
if buf, err = keys[i].value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i < olen-1 {
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
}
|
||||
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (obj *lazyObj) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, obj.force().String()...), nil
|
||||
}
|
||||
|
||||
func (s *set) AppendText(buf []byte) ([]byte, error) {
|
||||
slen := s.Len()
|
||||
if slen == 0 {
|
||||
return append(buf, "set()"...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
buf = append(buf, '{')
|
||||
if buf, err = AppendDelimeted(buf, s.sortedKeys(), ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (c Call) AppendText(buf []byte) ([]byte, error) {
|
||||
if len(c) == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if buf, err = c[0].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if buf, err = AppendDelimeted(append(buf, '('), c[1:], ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ')'), nil
|
||||
}
|
||||
|
||||
func (ts *TemplateString) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "$\""...)
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = x.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, '}')
|
||||
case *Term:
|
||||
if str, ok := x.Value.(String); ok {
|
||||
// TODO(anders): this is a bit of a mess, but as explained by the comment on
|
||||
// [EscapeTemplateStringStringPart], required as long as we rely on strconv for escaping, which adds
|
||||
// quotes around the string that we don't want here, and trying to "unappend" them is not nice at all..
|
||||
s := string(str)
|
||||
ulc := countUnescapedLeftCurly(s)
|
||||
sl := str.StringLength() + ulc - 2 // no surrounding quotes
|
||||
|
||||
if sl == len(s) { // no escaping needed
|
||||
buf = append(buf, s...)
|
||||
} else { // some escaping needed
|
||||
if sl == len(s)+ulc { // only unescaped {
|
||||
buf = AppendEscapedTemplateStringStringPart(buf, string(str))
|
||||
} else { // full escaping needed. this is expensive but luckily rare
|
||||
tmp := str.appendNoQuote(make([]byte, 0, sl))
|
||||
ets := EscapeTemplateStringStringPart(util.ByteSliceToString(tmp))
|
||||
buf = append(buf, ets...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if buf, err = x.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
buf = append(buf, "<invalid>"...)
|
||||
}
|
||||
}
|
||||
return append(buf, '"'), nil
|
||||
}
|
||||
|
||||
func (r Ref) AppendText(buf []byte) ([]byte, error) {
|
||||
reflen := len(r)
|
||||
if reflen == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
if reflen == 1 {
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
// While a ref head is typically a Var, a lone String term should not be quoted
|
||||
return append(buf, s...), nil
|
||||
}
|
||||
return r[0].AppendText(buf)
|
||||
}
|
||||
if name, ok := BuiltinNameFromRef(r); ok {
|
||||
return append(buf, name...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
buf = append(buf, s...)
|
||||
} else if buf, err = r[0].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, p := range r[1:] {
|
||||
switch v := p.Value.(type) {
|
||||
case String:
|
||||
str := string(v)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
buf = append(append(buf, '.'), str...)
|
||||
} else {
|
||||
buf = append(buf, '[')
|
||||
// Determine whether we need the full JSON-escaped form
|
||||
if strings.ContainsFunc(str, isControlOrBackslash) {
|
||||
if buf, err = v.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf = append(append(append(buf, '"'), str...), '"')
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
default:
|
||||
buf = append(buf, '[')
|
||||
if buf, err = p.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (sc *SetComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = sc.Term.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = sc.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (ac *ArrayComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '[')
|
||||
var err error
|
||||
if buf, err = ac.Term.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = ac.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ']'), nil
|
||||
}
|
||||
|
||||
func (oc *ObjectComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = oc.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ": "...)
|
||||
if buf, err = oc.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = oc.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
+5
@@ -25,6 +25,11 @@
|
||||
"Minor": 17,
|
||||
"Patch": 0
|
||||
},
|
||||
"array.flatten": {
|
||||
"Major": 1,
|
||||
"Minor": 13,
|
||||
"Patch": 0
|
||||
},
|
||||
"array.reverse": {
|
||||
"Major": 0,
|
||||
"Minor": 36,
|
||||
|
||||
+1
-2
@@ -14,7 +14,6 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
@@ -132,5 +131,5 @@ func encodePrimitive(v any) []byte {
|
||||
encoder := json.NewEncoder(&buf)
|
||||
encoder.SetEscapeHTML(false)
|
||||
_ = encoder.Encode(v)
|
||||
return []byte(strings.Trim(buf.String(), "\n"))
|
||||
return bytes.Trim(buf.Bytes(), "\n")
|
||||
}
|
||||
|
||||
+1
-3
@@ -9,7 +9,6 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -29,7 +28,6 @@ const defaultLocationFile = "__format_default__"
|
||||
var (
|
||||
expandedConst = ast.NewBody(ast.NewExpr(ast.InternedTerm(true)))
|
||||
commentsSlicePool = util.NewSlicePool[*ast.Comment](50)
|
||||
varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
|
||||
)
|
||||
|
||||
// Opts lets you control the code formatting via `AstWithOpts()`.
|
||||
@@ -1441,7 +1439,7 @@ func (w *writer) writeRefStringPath(s ast.String, l *ast.Location) {
|
||||
}
|
||||
|
||||
func (w *writer) shouldBracketRefTerm(s string, l *ast.Location) bool {
|
||||
if !varRegexp.MatchString(s) {
|
||||
if !ast.IsVarCompatibleString(s) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+38
-31
@@ -15,7 +15,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/report"
|
||||
"github.com/open-policy-agent/opa/internal/versioncheck"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
|
||||
@@ -140,6 +140,9 @@ const (
|
||||
type TriggerMode string
|
||||
|
||||
const (
|
||||
// TriggerImmediate represents uploading chunks when ready, flushed by the periodic polling mechanism
|
||||
TriggerImmediate TriggerMode = "immediate"
|
||||
|
||||
// TriggerPeriodic represents periodic polling mechanism
|
||||
TriggerPeriodic TriggerMode = "periodic"
|
||||
|
||||
@@ -212,11 +215,10 @@ type Manager struct {
|
||||
tracerProvider *trace.TracerProvider
|
||||
distributedTacingOpts tracing.Options
|
||||
registeredNDCacheTriggers []func(bool)
|
||||
registeredTelemetryGatherers map[string]report.Gatherer
|
||||
bootstrapConfigLabels map[string]string
|
||||
hooks hooks.Hooks
|
||||
enableTelemetry bool
|
||||
reporter report.Reporter
|
||||
enableVersionCheck bool
|
||||
versionChecker versioncheck.Checker
|
||||
opaReportNotifyCh chan struct{}
|
||||
stop chan chan struct{}
|
||||
parserOptions ast.ParserOptions
|
||||
@@ -272,10 +274,10 @@ func getWasmResolversOnContext(context *storage.Context) []*wasm.Resolver {
|
||||
|
||||
func validateTriggerMode(mode TriggerMode) error {
|
||||
switch mode {
|
||||
case TriggerPeriodic, TriggerManual:
|
||||
case TriggerPeriodic, TriggerManual, TriggerImmediate:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid trigger mode %q (want %q or %q)", mode, TriggerPeriodic, TriggerManual)
|
||||
return fmt.Errorf("invalid trigger mode %q (want %q, %q or %q)", mode, TriggerPeriodic, TriggerManual, TriggerImmediate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,18 +420,36 @@ func WithParserOptions(opts ast.ParserOptions) func(*Manager) {
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableTelemetry controls whether OPA will send telemetry reports to an external service.
|
||||
func WithEnableTelemetry(enableTelemetry bool) func(*Manager) {
|
||||
// WithEnableVersionCheck controls whether OPA will check for version updates.
|
||||
func WithEnableVersionCheck(enable bool) func(*Manager) {
|
||||
return func(m *Manager) {
|
||||
m.enableTelemetry = enableTelemetry
|
||||
m.enableVersionCheck = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableTelemetry controls whether OPA will check for version updates.
|
||||
//
|
||||
// Deprecated: please use WithEnableVersionCheck instead.
|
||||
func WithEnableTelemetry(enableTelemetry bool) func(*Manager) {
|
||||
return WithEnableVersionCheck(enableTelemetry)
|
||||
}
|
||||
|
||||
// WithTelemetryGatherers allows registration of telemetry gatherers which enable injection of additional data in the
|
||||
// telemetry report
|
||||
func WithTelemetryGatherers(gs map[string]report.Gatherer) func(*Manager) {
|
||||
//
|
||||
// Deprecated: This function is deprecated as telemetry gathering has been removed. Use WithVersionChecker to provide
|
||||
// a custom version checker implementation if needed.
|
||||
func WithTelemetryGatherers(gs map[string]versioncheck.Gatherer) func(*Manager) {
|
||||
return func(m *Manager) {
|
||||
m.registeredTelemetryGatherers = gs
|
||||
// No-op: telemetry gatherers are no longer used
|
||||
}
|
||||
}
|
||||
|
||||
// WithVersionChecker sets a custom version checker implementation.
|
||||
// If not provided, a default GitHub-based version checker will be used when telemetry is enabled.
|
||||
func WithVersionChecker(checker versioncheck.Checker) func(*Manager) {
|
||||
return func(m *Manager) {
|
||||
m.versionChecker = checker
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,25 +527,12 @@ func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*M
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.enableTelemetry {
|
||||
reporter, err := report.New(report.Options{Logger: m.logger})
|
||||
if m.enableVersionCheck {
|
||||
versionChecker, err := versioncheck.New(versioncheck.Options{Logger: m.logger})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.reporter = reporter
|
||||
|
||||
m.reporter.RegisterGatherer("min_compatible_version", func(_ context.Context) (any, error) {
|
||||
var minimumCompatibleVersion string
|
||||
if c := m.GetCompiler(); c != nil && c.Required != nil {
|
||||
minimumCompatibleVersion, _ = c.Required.MinimumCompatibleVersion()
|
||||
}
|
||||
return minimumCompatibleVersion, nil
|
||||
})
|
||||
|
||||
// register any additional gatherers
|
||||
for k, g := range m.registeredTelemetryGatherers {
|
||||
m.reporter.RegisterGatherer(k, g)
|
||||
}
|
||||
m.versionChecker = versionChecker
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -543,7 +550,7 @@ func (m *Manager) Init(ctx context.Context) error {
|
||||
Context: storage.NewContext(),
|
||||
}
|
||||
|
||||
if m.enableTelemetry {
|
||||
if m.enableVersionCheck {
|
||||
m.opaReportNotifyCh = make(chan struct{})
|
||||
m.stop = make(chan chan struct{})
|
||||
go m.sendOPAUpdateLoop(ctx)
|
||||
@@ -969,7 +976,7 @@ func (m *Manager) onCommit(ctx context.Context, txn storage.Transaction, event s
|
||||
if compiler != nil {
|
||||
m.setCompiler(compiler)
|
||||
|
||||
if m.enableTelemetry && event.PolicyChanged() {
|
||||
if m.enableVersionCheck && event.PolicyChanged() {
|
||||
m.opaReportNotifyCh <- struct{}{}
|
||||
}
|
||||
|
||||
@@ -1173,9 +1180,9 @@ func (m *Manager) sendOPAUpdateLoop(ctx context.Context) {
|
||||
|
||||
if opaReportNotify {
|
||||
opaReportNotify = false
|
||||
_, err := m.reporter.SendReport(ctx)
|
||||
_, err := m.versionChecker.LatestVersion(ctx)
|
||||
if err != nil {
|
||||
m.logger.WithFields(map[string]any{"err": err}).Debug("Unable to send OPA telemetry report.")
|
||||
m.logger.WithFields(map[string]any{"err": err}).Debug("Unable to check OPA version.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -21,9 +21,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-ini/ini"
|
||||
"github.com/open-policy-agent/opa/internal/providers/aws"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+10
@@ -1080,6 +1080,16 @@ func Store(s storage.Store) func(r *Rego) {
|
||||
}
|
||||
}
|
||||
|
||||
// Data returns an argument that sets the Rego data document. Data should be
|
||||
// a map representing the data document. This is a simpler alternative to
|
||||
// using Store with inmem.NewFromObject for cases where an in-memory store
|
||||
// with static data is sufficient.
|
||||
func Data(x map[string]any) func(r *Rego) {
|
||||
return func(r *Rego) {
|
||||
r.store = inmem.NewFromObject(x)
|
||||
}
|
||||
}
|
||||
|
||||
// StoreReadAST returns an argument that sets whether the store should eagerly convert data to AST values.
|
||||
//
|
||||
// Only applicable when no store has been set on the Rego object through the Store option.
|
||||
|
||||
+8
-4
@@ -45,12 +45,13 @@ func builtinSum(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err
|
||||
|
||||
// Non-integer values found, so we need to sum as floats.
|
||||
sum := big.NewFloat(0)
|
||||
tmp := new(big.Float)
|
||||
err := a.Iter(func(x *ast.Term) error {
|
||||
n, ok := x.Value.(ast.Number)
|
||||
if !ok {
|
||||
return builtins.NewOperandElementErr(1, a, x.Value, "number")
|
||||
}
|
||||
sum = new(big.Float).Add(sum, builtins.NumberToFloat(n))
|
||||
sum = new(big.Float).Add(sum, builtins.NumberToFloatInto(tmp, n))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -74,12 +75,13 @@ func builtinSum(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) err
|
||||
}
|
||||
|
||||
sum := big.NewFloat(0)
|
||||
tmp := new(big.Float)
|
||||
err := a.Iter(func(x *ast.Term) error {
|
||||
n, ok := x.Value.(ast.Number)
|
||||
if !ok {
|
||||
return builtins.NewOperandElementErr(1, a, x.Value, "number")
|
||||
}
|
||||
sum = new(big.Float).Add(sum, builtins.NumberToFloat(n))
|
||||
sum = new(big.Float).Add(sum, builtins.NumberToFloatInto(tmp, n))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -94,12 +96,13 @@ func builtinProduct(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term)
|
||||
switch a := operands[0].Value.(type) {
|
||||
case *ast.Array:
|
||||
product := big.NewFloat(1)
|
||||
tmp := new(big.Float)
|
||||
err := a.Iter(func(x *ast.Term) error {
|
||||
n, ok := x.Value.(ast.Number)
|
||||
if !ok {
|
||||
return builtins.NewOperandElementErr(1, a, x.Value, "number")
|
||||
}
|
||||
product = new(big.Float).Mul(product, builtins.NumberToFloat(n))
|
||||
product = new(big.Float).Mul(product, builtins.NumberToFloatInto(tmp, n))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -108,12 +111,13 @@ func builtinProduct(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term)
|
||||
return iter(ast.NewTerm(builtins.FloatToNumber(product)))
|
||||
case ast.Set:
|
||||
product := big.NewFloat(1)
|
||||
tmp := new(big.Float)
|
||||
err := a.Iter(func(x *ast.Term) error {
|
||||
n, ok := x.Value.(ast.Number)
|
||||
if !ok {
|
||||
return builtins.NewOperandElementErr(1, a, x.Value, "number")
|
||||
}
|
||||
product = new(big.Float).Mul(product, builtins.NumberToFloat(n))
|
||||
product = new(big.Float).Mul(product, builtins.NumberToFloatInto(tmp, n))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+46
-1
@@ -43,6 +43,42 @@ func builtinArrayConcat(_ BuiltinContext, operands []*ast.Term, iter func(*ast.T
|
||||
return iter(ast.ArrayTerm(arrC...))
|
||||
}
|
||||
|
||||
func builtinArrayFlatten(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
arr, err := builtins.ArrayOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := arr.Len()
|
||||
preAlloc := size
|
||||
containsArray := false
|
||||
|
||||
for i := range size {
|
||||
if nested, ok := arr.Elem(i).Value.(*ast.Array); ok {
|
||||
containsArray = true
|
||||
preAlloc += nested.Len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
if !containsArray && size == preAlloc {
|
||||
return iter(operands[0]) // Empty array, or no nested arrays -> nothing to flatten.
|
||||
}
|
||||
|
||||
flattened := make([]*ast.Term, 0, preAlloc)
|
||||
for i := range size {
|
||||
elem := arr.Elem(i)
|
||||
if nested, ok := elem.Value.(*ast.Array); ok {
|
||||
for j := range nested.Len() {
|
||||
flattened = append(flattened, nested.Elem(j))
|
||||
}
|
||||
} else {
|
||||
flattened = append(flattened, elem)
|
||||
}
|
||||
}
|
||||
|
||||
return iter(ast.ArrayTerm(flattened...))
|
||||
}
|
||||
|
||||
func builtinArraySlice(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
arr, err := builtins.ArrayOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
@@ -89,8 +125,16 @@ func builtinArrayReverse(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
}
|
||||
|
||||
length := arr.Len()
|
||||
reversedArr := make([]*ast.Term, length)
|
||||
|
||||
if length == 0 {
|
||||
return iter(ast.InternedEmptyArray)
|
||||
}
|
||||
|
||||
if length == 1 {
|
||||
return iter(operands[0])
|
||||
}
|
||||
|
||||
reversedArr := make([]*ast.Term, length)
|
||||
for index := range length {
|
||||
reversedArr[index] = arr.Elem(length - index - 1)
|
||||
}
|
||||
@@ -100,6 +144,7 @@ func builtinArrayReverse(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
|
||||
func init() {
|
||||
RegisterBuiltinFunc(ast.ArrayConcat.Name, builtinArrayConcat)
|
||||
RegisterBuiltinFunc(ast.ArrayFlatten.Name, builtinArrayFlatten)
|
||||
RegisterBuiltinFunc(ast.ArraySlice.Name, builtinArraySlice)
|
||||
RegisterBuiltinFunc(ast.ArrayReverse.Name, builtinArrayReverse)
|
||||
}
|
||||
|
||||
+10
-3
@@ -251,11 +251,18 @@ func ArrayOperand(x ast.Value, pos int) (*ast.Array, error) {
|
||||
|
||||
// NumberToFloat converts n to a big float.
|
||||
func NumberToFloat(n ast.Number) *big.Float {
|
||||
r, ok := new(big.Float).SetString(string(n))
|
||||
if !ok {
|
||||
return NumberToFloatInto(nil, n)
|
||||
}
|
||||
|
||||
// NumberToFloatInto converts n to a big float, storing it in dst when provided.
|
||||
func NumberToFloatInto(dst *big.Float, n ast.Number) *big.Float {
|
||||
if dst == nil {
|
||||
dst = new(big.Float)
|
||||
}
|
||||
if _, ok := dst.SetString(string(n)); !ok {
|
||||
panic("illegal value")
|
||||
}
|
||||
return r
|
||||
return dst
|
||||
}
|
||||
|
||||
// FloatToNumber converts f to a number.
|
||||
|
||||
+1
-1
@@ -300,7 +300,7 @@ func builtinNetCIDRMerge(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
|
||||
merged := evalNetCIDRMerge(networks)
|
||||
|
||||
result := ast.NewSet()
|
||||
result := ast.NewSetWithCapacity(len(merged))
|
||||
for _, network := range merged {
|
||||
result.Add(ast.StringTerm(network.String()))
|
||||
}
|
||||
|
||||
+1
-1
@@ -287,7 +287,7 @@ func builtinURLQueryDecodeObject(_ BuiltinContext, operands []*ast.Term, iter fu
|
||||
return err
|
||||
}
|
||||
|
||||
queryObject := ast.NewObject()
|
||||
queryObject := ast.NewObjectWithCapacity(len(queryParams))
|
||||
for k, v := range queryParams {
|
||||
paramsArray := make([]*ast.Term, len(v))
|
||||
for i, param := range v {
|
||||
|
||||
+20
-6
@@ -6,9 +6,9 @@ package topdown
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// Halt is a special error type that built-in function implementations return to indicate
|
||||
@@ -82,13 +82,27 @@ func (e *Error) Is(target error) bool {
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
msg := fmt.Sprintf("%v: %v", e.Code, e.Message)
|
||||
buf, _ := e.AppendText(make([]byte, 0, e.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (e *Error) AppendText(buf []byte) ([]byte, error) {
|
||||
if e.Location != nil {
|
||||
msg = e.Location.String() + ": " + msg
|
||||
buf, _ := e.Location.AppendText(buf)
|
||||
buf = append(append(buf, ": "...), e.Code...)
|
||||
buf = append(append(buf, ": "...), e.Message...)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
return msg
|
||||
return append(append(append(buf, e.Code...), ": "...), e.Message...), nil
|
||||
}
|
||||
|
||||
func (e *Error) StringLength() int {
|
||||
l := len(e.Code) + 2 + len(e.Message)
|
||||
if e.Location != nil {
|
||||
l += e.Location.StringLength() + 2
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (e *Error) Wrap(err error) *Error {
|
||||
@@ -124,11 +138,11 @@ func objectDocKeyConflictErr(loc *ast.Location) error {
|
||||
}
|
||||
}
|
||||
|
||||
func unsupportedBuiltinErr(loc *ast.Location) error {
|
||||
func unsupportedBuiltinErr(loc *ast.Location, name string) error {
|
||||
return &Error{
|
||||
Code: InternalErr,
|
||||
Location: loc,
|
||||
Message: "unsupported built-in",
|
||||
Message: "unsupported built-in: " + name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+36
-20
@@ -969,7 +969,7 @@ func (e *eval) evalCall(terms []*ast.Term, iter unifyIterator) error {
|
||||
builtinName := ref.String()
|
||||
bi, f, ok := e.builtinFunc(builtinName)
|
||||
if !ok {
|
||||
return unsupportedBuiltinErr(e.query[e.index].Location)
|
||||
return unsupportedBuiltinErr(e.query[e.index].Location, builtinName)
|
||||
}
|
||||
|
||||
if mocked { // value replacement of built-in call
|
||||
@@ -1492,20 +1492,20 @@ func (e *eval) amendComprehension(a *ast.Term, b1 *bindings) (*ast.Term, error)
|
||||
}
|
||||
|
||||
func (e *eval) biunifyComprehensionArray(x *ast.ArrayComprehension, b *ast.Term, b1, b2 *bindings, iter unifyIterator) error {
|
||||
result := ast.NewArray()
|
||||
var elements []*ast.Term
|
||||
child := evalPool.Get()
|
||||
|
||||
e.closure(x.Body, child)
|
||||
defer evalPool.Put(child)
|
||||
|
||||
err := child.Run(func(child *eval) error {
|
||||
result = result.Append(child.bindings.Plug(x.Term))
|
||||
elements = append(elements, child.bindings.Plug(x.Term))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.biunify(ast.NewTerm(result), b, b1, b2, iter)
|
||||
return e.biunify(ast.NewTerm(ast.NewArray(elements...)), b, b1, b2, iter)
|
||||
}
|
||||
|
||||
func (e *eval) biunifyComprehensionSet(x *ast.SetComprehension, b *ast.Term, b1, b2 *bindings, iter unifyIterator) error {
|
||||
@@ -2486,6 +2486,20 @@ func (e evalTree) next(iter unifyIterator, plugged *ast.Term) error {
|
||||
return cpy.eval(iter)
|
||||
}
|
||||
|
||||
// enumerateNext is a helper to avoid closure allocation in enumerate loops.
|
||||
// Method values don't allocate, unlike explicit closures.
|
||||
// Using a pointer to evalTree avoids copying the 96-byte structure.
|
||||
// Fields are ordered by size for optimal memory alignment (16 > 8 > 8 bytes).
|
||||
type enumerateNext struct {
|
||||
iter unifyIterator // 16 bytes (interface)
|
||||
e *evalTree // 8 bytes (pointer)
|
||||
key *ast.Term // 8 bytes (pointer)
|
||||
}
|
||||
|
||||
func (en *enumerateNext) call() error {
|
||||
return en.e.next(en.iter, en.key)
|
||||
}
|
||||
|
||||
func (e evalTree) enumerate(iter unifyIterator) error {
|
||||
|
||||
if e.e.inliningControl.Disabled(e.plugged[:e.pos], true) {
|
||||
@@ -2501,14 +2515,17 @@ func (e evalTree) enumerate(iter unifyIterator) error {
|
||||
dc.deferred = nil
|
||||
defer deecPool.Put(dc)
|
||||
|
||||
// Use method value to avoid closure allocation.
|
||||
// Create once and reuse for both doc and virtual doc enumeration.
|
||||
en := enumerateNext{iter: iter, e: &e, key: nil}
|
||||
|
||||
if doc != nil {
|
||||
switch doc := doc.(type) {
|
||||
case *ast.Array:
|
||||
for i := range doc.Len() {
|
||||
k := ast.InternedTerm(i)
|
||||
err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, func() error {
|
||||
return e.next(iter, k)
|
||||
})
|
||||
en.key = k
|
||||
err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, en.call)
|
||||
|
||||
if err := dc.handleErr(err); err != nil {
|
||||
return err
|
||||
@@ -2517,21 +2534,20 @@ func (e evalTree) enumerate(iter unifyIterator) error {
|
||||
case ast.Object:
|
||||
ki := doc.KeysIterator()
|
||||
for k, more := ki.Next(); more; k, more = ki.Next() {
|
||||
err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, func() error {
|
||||
return e.next(iter, k)
|
||||
})
|
||||
en.key = k
|
||||
err := e.e.biunify(k, e.ref[e.pos], e.bindings, e.bindings, en.call)
|
||||
if err := dc.handleErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ast.Set:
|
||||
if err := doc.Iter(func(elem *ast.Term) error {
|
||||
err := e.e.biunify(elem, e.ref[e.pos], e.bindings, e.bindings, func() error {
|
||||
return e.next(iter, elem)
|
||||
})
|
||||
return dc.handleErr(err)
|
||||
}); err != nil {
|
||||
return err
|
||||
// Use Slice() to avoid closure allocation in Iter()
|
||||
for _, elem := range doc.Slice() {
|
||||
en.key = elem
|
||||
err := e.e.biunify(elem, e.ref[e.pos], e.bindings, e.bindings, en.call)
|
||||
if err := dc.handleErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2544,11 +2560,11 @@ func (e evalTree) enumerate(iter unifyIterator) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reuse the same enumerateNext for virtual documents
|
||||
for _, k := range e.node.Sorted {
|
||||
key := ast.NewTerm(k)
|
||||
if err := e.e.biunify(key, e.ref[e.pos], e.bindings, e.bindings, func() error {
|
||||
return e.next(iter, key)
|
||||
}); err != nil {
|
||||
en.key = key
|
||||
if err := e.e.biunify(key, e.ref[e.pos], e.bindings, e.bindings, en.call); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -146,7 +146,7 @@ func pruneIrrelevantGraphQLASTNodes(value ast.Value) ast.Value {
|
||||
// extant ast type!
|
||||
switch x := value.(type) {
|
||||
case *ast.Array:
|
||||
result := ast.NewArray()
|
||||
result := ast.NewArrayWithCapacity(x.Len())
|
||||
// Iterate over the array's elements, and do the following:
|
||||
// - Drop any Nulls
|
||||
// - Drop any any empty object/array value (after running the pruner)
|
||||
@@ -173,7 +173,7 @@ func pruneIrrelevantGraphQLASTNodes(value ast.Value) ast.Value {
|
||||
}
|
||||
return result
|
||||
case ast.Object:
|
||||
result := ast.NewObject()
|
||||
result := ast.NewObjectWithCapacity(x.Len())
|
||||
// Iterate over our object's keys, and do the following:
|
||||
// - Drop "Position".
|
||||
// - Drop any key with a Null value.
|
||||
|
||||
+6
-6
@@ -65,7 +65,7 @@ func jsonRemove(a *ast.Term, b *ast.Term) (*ast.Term, error) {
|
||||
case ast.String, ast.Number, ast.Boolean, ast.Null:
|
||||
return a, nil
|
||||
case ast.Object:
|
||||
newObj := ast.NewObject()
|
||||
newObj := ast.NewObjectWithCapacity(aValue.Len())
|
||||
err := aValue.Iter(func(k *ast.Term, v *ast.Term) error {
|
||||
// recurse and add the diff of sub objects as needed
|
||||
diffValue, err := jsonRemove(v, bObj.Get(k))
|
||||
@@ -80,7 +80,7 @@ func jsonRemove(a *ast.Term, b *ast.Term) (*ast.Term, error) {
|
||||
}
|
||||
return ast.NewTerm(newObj), nil
|
||||
case ast.Set:
|
||||
newSet := ast.NewSet()
|
||||
newSet := ast.NewSetWithCapacity(aValue.Len())
|
||||
err := aValue.Iter(func(v *ast.Term) error {
|
||||
// recurse and add the diff of sub objects as needed
|
||||
diffValue, err := jsonRemove(v, bObj.Get(v))
|
||||
@@ -97,7 +97,7 @@ func jsonRemove(a *ast.Term, b *ast.Term) (*ast.Term, error) {
|
||||
case *ast.Array:
|
||||
// When indexes are removed we shift left to close empty spots in the array
|
||||
// as per the JSON patch spec.
|
||||
newArray := ast.NewArray()
|
||||
newArraySlice := make([]*ast.Term, 0, aValue.Len())
|
||||
for i := range aValue.Len() {
|
||||
v := aValue.Elem(i)
|
||||
// recurse and add the diff of sub objects as needed
|
||||
@@ -107,10 +107,10 @@ func jsonRemove(a *ast.Term, b *ast.Term) (*ast.Term, error) {
|
||||
return nil, err
|
||||
}
|
||||
if diffValue != nil {
|
||||
newArray = newArray.Append(diffValue)
|
||||
newArraySlice = append(newArraySlice, diffValue)
|
||||
}
|
||||
}
|
||||
return ast.NewTerm(newArray), nil
|
||||
return ast.NewTerm(ast.NewArray(newArraySlice...)), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid value type %T", a)
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func parsePath(path *ast.Term) (ast.Ref, error) {
|
||||
}
|
||||
|
||||
func pathsToObject(paths []ast.Ref) ast.Object {
|
||||
root := ast.NewObject()
|
||||
root := ast.NewObjectWithCapacity(len(paths))
|
||||
|
||||
for _, path := range paths {
|
||||
node := root
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ func astValueToJSONSchemaLoader(value ast.Value) (gojsonschema.JSONLoader, error
|
||||
return nil, errors.New("invalid JSON string")
|
||||
}
|
||||
loader = gojsonschema.NewStringLoader(string(x))
|
||||
case ast.Object:
|
||||
case ast.Object, *ast.Array:
|
||||
// In case of object serialize it to JSON representation.
|
||||
var data any
|
||||
data, err = ast.JSON(value)
|
||||
@@ -110,7 +110,7 @@ func builtinJSONMatchSchema(bctx BuiltinContext, operands []*ast.Term, iter func
|
||||
}
|
||||
|
||||
// In case of validation errors produce Rego array of objects to describe the errors.
|
||||
arr := ast.NewArray()
|
||||
arr := ast.NewArrayWithCapacity(len(result.Errors()))
|
||||
for _, re := range result.Errors() {
|
||||
o := ast.NewObject(
|
||||
[...]*ast.Term{ast.StringTerm("error"), ast.StringTerm(re.String())},
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ func builtinLookupIPAddr(bctx BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
return err
|
||||
}
|
||||
|
||||
ret := ast.NewSet()
|
||||
ret := ast.NewSetWithCapacity(len(addrs))
|
||||
for _, a := range addrs {
|
||||
ret.Add(ast.StringTerm(a.String()))
|
||||
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ func canGenerateCheapRange(operands []*ast.Term) bool {
|
||||
|
||||
func canGenerateCheapRangeStep(operands []*ast.Term) bool {
|
||||
if canGenerateCheapRange(operands) {
|
||||
step, err := builtins.IntOperand(operands[1].Value, 3)
|
||||
step, err := builtins.IntOperand(operands[2].Value, 3)
|
||||
if err == nil && ast.HasInternedIntNumberTerm(step) {
|
||||
return true
|
||||
}
|
||||
|
||||
+23
-8
@@ -52,13 +52,21 @@ func builtinObjectUnionN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
// Example:
|
||||
// Input: [{"a": {"b": 2}}, {"a": 4}, {"a": {"c": 3}}]
|
||||
// Want Output: {"a": {"c": 3}}
|
||||
result := ast.NewObject()
|
||||
frozenKeys := map[*ast.Term]struct{}{}
|
||||
for i := arr.Len() - 1; i >= 0; i-- {
|
||||
|
||||
// First pass: count total keys for pre-allocation
|
||||
totalSize := 0
|
||||
for i := range arr.Len() {
|
||||
o, ok := arr.Elem(i).Value.(ast.Object)
|
||||
if !ok {
|
||||
return builtins.NewOperandElementErr(1, arr, arr.Elem(i).Value, "object")
|
||||
}
|
||||
totalSize += o.Len()
|
||||
}
|
||||
|
||||
result := ast.NewObjectWithCapacity(totalSize)
|
||||
frozenKeys := make(map[*ast.Term]struct{}, totalSize)
|
||||
for i := arr.Len() - 1; i >= 0; i-- {
|
||||
o := arr.Elem(i).Value.(ast.Object) // Already validated above
|
||||
mergewithOverwriteInPlace(result, o, frozenKeys)
|
||||
}
|
||||
|
||||
@@ -77,7 +85,9 @@ func builtinObjectRemove(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := ast.NewObject()
|
||||
|
||||
// Pre-allocate with obj size (upper bound for result)
|
||||
r := ast.NewObjectWithCapacity(obj.Len())
|
||||
obj.Foreach(func(key *ast.Term, value *ast.Term) {
|
||||
if !keysToRemove.Contains(key) {
|
||||
r.Insert(key, value)
|
||||
@@ -100,7 +110,8 @@ func builtinObjectFilter(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
return err
|
||||
}
|
||||
|
||||
filterObj := ast.NewObject()
|
||||
// Pre-allocate with keys size (upper bound for filter object)
|
||||
filterObj := ast.NewObjectWithCapacity(keys.Len())
|
||||
keys.Foreach(func(key *ast.Term) {
|
||||
filterObj.Insert(key, ast.InternedNullTerm)
|
||||
})
|
||||
@@ -158,15 +169,19 @@ func builtinObjectKeys(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
}
|
||||
|
||||
// getObjectKeysParam returns a set of key values
|
||||
// from a supplied ast array, object, set value
|
||||
// from a supplied ast array, object, set value.
|
||||
// The returned set must not be mutated. For Set
|
||||
// inputs, it may be the original.
|
||||
func getObjectKeysParam(arrayOrSet ast.Value) (ast.Set, error) {
|
||||
switch v := arrayOrSet.(type) {
|
||||
case *ast.Array:
|
||||
keys := ast.NewSet()
|
||||
keys := ast.NewSetWithCapacity(v.Len())
|
||||
v.Foreach(keys.Add)
|
||||
return keys, nil
|
||||
case ast.Set:
|
||||
return ast.NewSet(v.Slice()...), nil
|
||||
// Return directly. Callers only use this for Contains() checks
|
||||
// without mutating the set.
|
||||
return v, nil
|
||||
case ast.Object:
|
||||
return ast.NewSet(v.Keys()...), nil
|
||||
}
|
||||
|
||||
+35
-1
@@ -266,7 +266,41 @@ func builtinRegexReplace(bctx BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
return err
|
||||
}
|
||||
|
||||
res := re.ReplaceAllString(string(base), string(value))
|
||||
// If no cancellation context, use the fast path
|
||||
if bctx.Cancel == nil {
|
||||
res := re.ReplaceAllString(string(base), string(value))
|
||||
if res == string(base) {
|
||||
return iter(operands[0])
|
||||
}
|
||||
return iter(ast.InternedTerm(res))
|
||||
}
|
||||
|
||||
// Use sink writer for cancellation-aware replacement
|
||||
sink := newSink(ast.RegexReplace.Name, len(base), bctx.Cancel)
|
||||
src := []byte(base)
|
||||
repl := []byte(value)
|
||||
|
||||
// Find all matches at once to preserve anchor behavior: replace("foo", "^[a-z]", "F") => "Foo"
|
||||
allMatches := re.FindAllSubmatchIndex(src, -1)
|
||||
|
||||
lastEnd := 0
|
||||
for _, match := range allMatches {
|
||||
if _, err := sink.Write(src[lastEnd:match[0]]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sink.Write(re.Expand(nil, repl, src, match)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastEnd = match[1]
|
||||
}
|
||||
|
||||
if _, err := sink.Write(src[lastEnd:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := sink.String()
|
||||
if res == string(base) {
|
||||
return iter(operands[0])
|
||||
}
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ func (t *resolverTrie) mktree(e *eval, in resolver.Input) (ast.Value, error) {
|
||||
}
|
||||
return result.Value, nil
|
||||
}
|
||||
obj := ast.NewObject()
|
||||
obj := ast.NewObjectWithCapacity(len(t.children))
|
||||
for k, child := range t.children {
|
||||
v, err := child.mktree(e, resolver.Input{Ref: append(in.Ref, ast.NewTerm(k)), Input: in.Input, Metrics: in.Metrics})
|
||||
if err != nil {
|
||||
|
||||
+18
-6
@@ -61,22 +61,34 @@ func builtinSetIntersection(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
|
||||
// builtinSetUnion returns the union of the given input sets
|
||||
func builtinSetUnion(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// The set union logic here is duplicated and manually inlined on
|
||||
// purpose. By lifting this logic up a level, and not doing pairwise
|
||||
// set unions, we avoid a number of heap allocations. This improves
|
||||
// performance dramatically over the naive approach.
|
||||
result := ast.NewSet()
|
||||
|
||||
// The set union logic here is manually inlined on purpose. By lifting
|
||||
// this logic up a level and not doing pairwise set unions, we avoid
|
||||
// many heap allocations. We also pre-allocate the result set by first
|
||||
// counting total elements across all input sets.
|
||||
inputSet, err := builtins.SetOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// First pass: count total elements for pre-allocation
|
||||
totalSize := 0
|
||||
err = inputSet.Iter(func(x *ast.Term) error {
|
||||
item, err := builtins.SetOperand(x.Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
totalSize += item.Len()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pre-allocate result set with estimated capacity
|
||||
result := ast.NewSetWithCapacity(totalSize)
|
||||
|
||||
err = inputSet.Iter(func(x *ast.Term) error {
|
||||
item, _ := builtins.SetOperand(x.Value, 1) // error checked above
|
||||
item.Foreach(result.Add)
|
||||
return nil
|
||||
})
|
||||
|
||||
+4
-3
@@ -614,12 +614,13 @@ func builtinTrim(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) er
|
||||
return err
|
||||
}
|
||||
|
||||
trimmed := strings.Trim(string(s), string(c))
|
||||
if trimmed == string(s) {
|
||||
str := string(s)
|
||||
trimmed := strings.Trim(str, string(c))
|
||||
if trimmed == str {
|
||||
return iter(operands[0])
|
||||
}
|
||||
|
||||
return iter(ast.InternedTerm(strings.Trim(string(s), string(c))))
|
||||
return iter(ast.InternedTerm(trimmed))
|
||||
}
|
||||
|
||||
func builtinTrimLeft(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
|
||||
+3
-3
@@ -2,6 +2,7 @@ package topdown
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
@@ -30,14 +31,13 @@ func renderTemplate(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term)
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not attempt to render if template variable keys are missing
|
||||
tmpl.Option("missingkey=error")
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, templateVariables); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return iter(ast.StringTerm(buf.String()))
|
||||
res := strings.ReplaceAll(buf.String(), "<no value>", "<undefined>")
|
||||
return iter(ast.StringTerm(res))
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+6
@@ -2,6 +2,7 @@ package util
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
@@ -108,6 +109,11 @@ func NumDigitsUint(n uint64) int {
|
||||
return count
|
||||
}
|
||||
|
||||
// AppendInt is a less messy version of strconv.AppendInt for base 10 ints.
|
||||
func AppendInt(buf []byte, n int) []byte {
|
||||
return strconv.AppendInt(buf, int64(n), 10)
|
||||
}
|
||||
|
||||
// SplitMap calls fn for each delim-separated part of text and returns a slice of the results.
|
||||
// Cheaper than calling fn on strings.Split(text, delim), as it avoids allocating an intermediate slice of strings.
|
||||
func SplitMap[T any](text string, delim string, fn func(string) T) []T {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var Version = "1.12.3"
|
||||
var Version = "1.13.1"
|
||||
|
||||
// GoVersion is the version of Go this was built with
|
||||
var GoVersion = runtime.Version()
|
||||
|
||||
Reference in New Issue
Block a user