bump dependencies
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
+1
-1
@@ -520,7 +520,7 @@ func attachRuleAnnotations(mod *Module) {
|
||||
var j int
|
||||
var found bool
|
||||
for i, a := range cpy {
|
||||
if rule.Ref().Equal(a.GetTargetPath()) {
|
||||
if rule.Ref().GroundPrefix().Equal(a.GetTargetPath()) {
|
||||
if a.Scope == annotationScopeDocument {
|
||||
rule.Annotations = append(rule.Annotations, a)
|
||||
} else if a.Scope == annotationScopeRule && rule.Loc().Row > a.Location.Row {
|
||||
|
||||
+36
-11
@@ -33,15 +33,18 @@ type typeChecker struct {
|
||||
allowNet []string
|
||||
input types.Type
|
||||
allowUndefinedFuncs bool
|
||||
schemaTypes map[string]types.Type
|
||||
}
|
||||
|
||||
// newTypeChecker returns a new typeChecker object that has no errors.
|
||||
func newTypeChecker() *typeChecker {
|
||||
tc := &typeChecker{}
|
||||
tc.exprCheckers = map[string]exprChecker{
|
||||
"eq": tc.checkExprEq,
|
||||
return &typeChecker{
|
||||
builtins: make(map[string]*Builtin),
|
||||
schemaTypes: make(map[string]types.Type),
|
||||
exprCheckers: map[string]exprChecker{
|
||||
"eq": checkExprEq,
|
||||
},
|
||||
}
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *typeChecker) newEnv(exist *TypeEnv) *TypeEnv {
|
||||
@@ -196,20 +199,42 @@ func (tc *typeChecker) checkClosures(env *TypeEnv, expr *Expr) Errors {
|
||||
return result
|
||||
}
|
||||
|
||||
func (tc *typeChecker) getSchemaType(schemaAnnot *SchemaAnnotation, rule *Rule) (types.Type, *Error) {
|
||||
if refType, exists := tc.schemaTypes[schemaAnnot.Schema.String()]; exists {
|
||||
return refType, nil
|
||||
}
|
||||
|
||||
refType, err := processAnnotation(tc.ss, schemaAnnot, rule, tc.allowNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if refType == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tc.schemaTypes[schemaAnnot.Schema.String()] = refType
|
||||
return refType, nil
|
||||
|
||||
}
|
||||
|
||||
func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
|
||||
|
||||
env = env.wrap()
|
||||
|
||||
schemaAnnots := getRuleAnnotation(as, rule)
|
||||
for _, schemaAnnot := range schemaAnnots {
|
||||
ref, refType, err := processAnnotation(tc.ss, schemaAnnot, rule, tc.allowNet)
|
||||
refType, err := tc.getSchemaType(schemaAnnot, rule)
|
||||
if err != nil {
|
||||
tc.err([]*Error{err})
|
||||
continue
|
||||
}
|
||||
|
||||
ref := schemaAnnot.Path
|
||||
if ref == nil && refType == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixRef, t := getPrefix(env, ref)
|
||||
if t == nil || len(prefixRef) == len(ref) {
|
||||
env.tree.Put(ref, refType)
|
||||
@@ -404,7 +429,7 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *typeChecker) checkExprEq(env *TypeEnv, expr *Expr) *Error {
|
||||
func checkExprEq(env *TypeEnv, expr *Expr) *Error {
|
||||
|
||||
pre := getArgTypes(env, expr.Operands())
|
||||
exp := Equality.Decl.FuncArgs()
|
||||
@@ -1266,17 +1291,17 @@ func getRuleAnnotation(as *AnnotationSet, rule *Rule) (result []*SchemaAnnotatio
|
||||
return result
|
||||
}
|
||||
|
||||
func processAnnotation(ss *SchemaSet, annot *SchemaAnnotation, rule *Rule, allowNet []string) (Ref, types.Type, *Error) {
|
||||
func processAnnotation(ss *SchemaSet, annot *SchemaAnnotation, rule *Rule, allowNet []string) (types.Type, *Error) {
|
||||
|
||||
var schema interface{}
|
||||
|
||||
if annot.Schema != nil {
|
||||
if ss == nil {
|
||||
return nil, nil, nil
|
||||
return nil, nil
|
||||
}
|
||||
schema = ss.Get(annot.Schema)
|
||||
if schema == nil {
|
||||
return nil, nil, NewError(TypeErr, rule.Location, "undefined schema: %v", annot.Schema)
|
||||
return nil, NewError(TypeErr, rule.Location, "undefined schema: %v", annot.Schema)
|
||||
}
|
||||
} else if annot.Definition != nil {
|
||||
schema = *annot.Definition
|
||||
@@ -1284,10 +1309,10 @@ func processAnnotation(ss *SchemaSet, annot *SchemaAnnotation, rule *Rule, allow
|
||||
|
||||
tpe, err := loadSchema(schema, allowNet)
|
||||
if err != nil {
|
||||
return nil, nil, NewError(TypeErr, rule.Location, err.Error())
|
||||
return nil, NewError(TypeErr, rule.Location, err.Error())
|
||||
}
|
||||
|
||||
return annot.Path, tpe, nil
|
||||
return tpe, nil
|
||||
}
|
||||
|
||||
func errAnnotationRedeclared(a *Annotations, other *Location) *Error {
|
||||
|
||||
+27
-10
@@ -5332,8 +5332,7 @@ func rewriteDeclaredVarsInExpr(g *localVarGenerator, stack *localDeclaredVars, e
|
||||
case *Term:
|
||||
stop, errs = rewriteDeclaredVarsInTerm(g, stack, x, errs, strict)
|
||||
case *With:
|
||||
errs = rewriteDeclaredVarsInTermRecursive(g, stack, x.Value, errs, strict)
|
||||
stop = true
|
||||
stop, errs = true, rewriteDeclaredVarsInWithRecursive(g, stack, x, errs, strict)
|
||||
}
|
||||
return stop
|
||||
})
|
||||
@@ -5466,20 +5465,38 @@ func rewriteDeclaredVarsInTerm(g *localVarGenerator, stack *localDeclaredVars, t
|
||||
}
|
||||
|
||||
func rewriteDeclaredVarsInTermRecursive(g *localVarGenerator, stack *localDeclaredVars, term *Term, errs Errors, strict bool) Errors {
|
||||
WalkNodes(term, func(n Node) bool {
|
||||
WalkTerms(term, func(t *Term) bool {
|
||||
var stop bool
|
||||
switch n := n.(type) {
|
||||
case *With:
|
||||
errs = rewriteDeclaredVarsInTermRecursive(g, stack, n.Value, errs, strict)
|
||||
stop = true
|
||||
case *Term:
|
||||
stop, errs = rewriteDeclaredVarsInTerm(g, stack, n, errs, strict)
|
||||
}
|
||||
stop, errs = rewriteDeclaredVarsInTerm(g, stack, t, errs, strict)
|
||||
return stop
|
||||
})
|
||||
return errs
|
||||
}
|
||||
|
||||
func rewriteDeclaredVarsInWithRecursive(g *localVarGenerator, stack *localDeclaredVars, w *With, errs Errors, strict bool) Errors {
|
||||
// NOTE(sr): `with input as` and `with input.a.b.c as` are deliberately skipped here: `input` could
|
||||
// have been shadowed by a local variable/argument but should NOT be replaced in the `with` target.
|
||||
//
|
||||
// We cannot drop `input` from the stack since it's conceivable to do `with input[input] as` where
|
||||
// the second input is meant to be the local var. It's a terrible idea, but when you're shadowing
|
||||
// `input` those might be your thing.
|
||||
errs = rewriteDeclaredVarsInTermRecursive(g, stack, w.Target, errs, strict)
|
||||
if sdwInput, ok := stack.Declared(InputRootDocument.Value.(Var)); ok { // Was "input" shadowed...
|
||||
switch value := w.Target.Value.(type) {
|
||||
case Var:
|
||||
if sdwInput.Equal(value) { // ...and replaced? If so, fix it
|
||||
w.Target.Value = InputRootRef
|
||||
}
|
||||
case Ref:
|
||||
if sdwInput.Equal(value[0].Value.(Var)) {
|
||||
w.Target.Value.(Ref)[0].Value = InputRootDocument.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
// No special handling of the `with` value
|
||||
return rewriteDeclaredVarsInTermRecursive(g, stack, w.Value, errs, strict)
|
||||
}
|
||||
|
||||
func rewriteDeclaredVarsInArrayComprehension(g *localVarGenerator, stack *localDeclaredVars, v *ArrayComprehension, errs Errors, strict bool) Errors {
|
||||
used := NewVarSet()
|
||||
used.Update(v.Term.Vars())
|
||||
|
||||
+38
-6
@@ -30,6 +30,8 @@ var RegoV1CompatibleRef = Ref{VarTerm("rego"), StringTerm("v1")}
|
||||
// RegoVersion defines the Rego syntax requirements for a module.
|
||||
type RegoVersion int
|
||||
|
||||
const DefaultRegoVersion = RegoVersion(0)
|
||||
|
||||
const (
|
||||
// RegoV0 is the default, original Rego syntax.
|
||||
RegoV0 RegoVersion = iota
|
||||
@@ -317,6 +319,31 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {
|
||||
for k, v := range futureKeywords {
|
||||
allowedFutureKeywords[k] = v
|
||||
}
|
||||
|
||||
// For sake of error reporting, we still need to check that keywords in capabilities are known,
|
||||
for _, kw := range p.po.Capabilities.FutureKeywords {
|
||||
if _, ok := futureKeywords[kw]; !ok {
|
||||
return nil, nil, Errors{
|
||||
&Error{
|
||||
Code: ParseErr,
|
||||
Message: fmt.Sprintf("illegal capabilities: unknown keyword: %v", kw),
|
||||
Location: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// and that explicitly requested future keywords are known.
|
||||
for _, kw := range p.po.FutureKeywords {
|
||||
if _, ok := allowedFutureKeywords[kw]; !ok {
|
||||
return nil, nil, Errors{
|
||||
&Error{
|
||||
Code: ParseErr,
|
||||
Message: fmt.Sprintf("unknown future keyword: %v", kw),
|
||||
Location: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, kw := range p.po.Capabilities.FutureKeywords {
|
||||
var ok bool
|
||||
@@ -686,6 +713,10 @@ func (p *Parser) parseRules() []*Rule {
|
||||
|
||||
// p[x] if ... becomes a single-value rule p[x]
|
||||
if hasIf && !usesContains && len(rule.Head.Ref()) == 2 {
|
||||
if !rule.Head.Ref()[1].IsGround() && len(rule.Head.Args) == 0 {
|
||||
rule.Head.Key = rule.Head.Ref()[1]
|
||||
}
|
||||
|
||||
if rule.Head.Value == nil {
|
||||
rule.Head.generatedValue = true
|
||||
rule.Head.Value = BooleanTerm(true).SetLocation(rule.Head.Location)
|
||||
@@ -2665,15 +2696,16 @@ func (p *Parser) regoV1Import(imp *Import) {
|
||||
return
|
||||
}
|
||||
|
||||
if p.po.RegoVersion == RegoV1 {
|
||||
// We're parsing for Rego v1, where the 'rego.v1' import is a no-op.
|
||||
path := imp.Path.Value.(Ref)
|
||||
|
||||
// v1 is only valid option
|
||||
if len(path) == 1 || !path[1].Equal(RegoV1CompatibleRef[1]) || len(path) > 2 {
|
||||
p.errorf(imp.Path.Location, "invalid import `%s`, must be `%s`", path, RegoV1CompatibleRef)
|
||||
return
|
||||
}
|
||||
|
||||
path := imp.Path.Value.(Ref)
|
||||
|
||||
if len(path) == 1 || !path[1].Equal(RegoV1CompatibleRef[1]) || len(path) > 2 {
|
||||
p.errorf(imp.Path.Location, "invalid import `%s`, must be `%s`", path, RegoV1CompatibleRef)
|
||||
if p.po.RegoVersion == RegoV1 {
|
||||
// We're parsing for Rego v1, where the 'rego.v1' import is a no-op.
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+35
-5
@@ -103,6 +103,14 @@ func MustParseStatement(input string) Statement {
|
||||
return parsed
|
||||
}
|
||||
|
||||
func MustParseStatementWithOpts(input string, popts ParserOptions) Statement {
|
||||
parsed, err := ParseStatementWithOpts(input, popts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseRef returns a parsed reference.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseRef(input string) Ref {
|
||||
@@ -123,6 +131,16 @@ func MustParseRule(input string) *Rule {
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseRuleWithOpts returns a parsed rule.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseRuleWithOpts(input string, opts ParserOptions) *Rule {
|
||||
parsed, err := ParseRuleWithOpts(input, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseTerm returns a parsed term.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseTerm(input string) *Term {
|
||||
@@ -269,11 +287,12 @@ func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, erro
|
||||
setJSONOptions(body, &rhs.jsonOptions)
|
||||
|
||||
return &Rule{
|
||||
Location: lhs.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
jsonOptions: lhs.jsonOptions,
|
||||
Location: lhs.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
jsonOptions: lhs.jsonOptions,
|
||||
generatedBody: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -608,6 +627,17 @@ func ParseStatement(input string) (Statement, error) {
|
||||
return stmts[0], nil
|
||||
}
|
||||
|
||||
func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error) {
|
||||
stmts, _, err := ParseStatementsWithOpts("", input, popts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(stmts) != 1 {
|
||||
return nil, fmt.Errorf("expected exactly one statement")
|
||||
}
|
||||
return stmts[0], nil
|
||||
}
|
||||
|
||||
// ParseStatements is deprecated. Use ParseStatementWithOpts instead.
|
||||
func ParseStatements(filename, input string) ([]Statement, []*Comment, error) {
|
||||
return ParseStatementsWithOpts(filename, input, ParserOptions{})
|
||||
|
||||
+54
-11
@@ -100,7 +100,7 @@ var Wildcard = &Term{Value: Var("_")}
|
||||
var WildcardPrefix = "$"
|
||||
|
||||
// Keywords contains strings that map to language keywords.
|
||||
var Keywords = KeywordsV0
|
||||
var Keywords = KeywordsForRegoVersion(DefaultRegoVersion)
|
||||
|
||||
var KeywordsV0 = [...]string{
|
||||
"not",
|
||||
@@ -134,9 +134,23 @@ var KeywordsV1 = [...]string{
|
||||
"every",
|
||||
}
|
||||
|
||||
func KeywordsForRegoVersion(v RegoVersion) []string {
|
||||
switch v {
|
||||
case RegoV0:
|
||||
return KeywordsV0[:]
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
return KeywordsV1[:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsKeyword returns true if s is a language keyword.
|
||||
func IsKeyword(s string) bool {
|
||||
for _, x := range Keywords {
|
||||
return IsInKeywords(s, Keywords)
|
||||
}
|
||||
|
||||
func IsInKeywords(s string, keywords []string) bool {
|
||||
for _, x := range keywords {
|
||||
if x == s {
|
||||
return true
|
||||
}
|
||||
@@ -405,7 +419,7 @@ func (mod *Module) String() string {
|
||||
buf = append(buf, "")
|
||||
for _, rule := range mod.Rules {
|
||||
buf = appendAnnotationStrings(buf, rule)
|
||||
buf = append(buf, rule.String())
|
||||
buf = append(buf, rule.stringWithOpts(toStringOpts{regoVersion: mod.regoVersion}))
|
||||
}
|
||||
}
|
||||
return strings.Join(buf, "\n")
|
||||
@@ -770,18 +784,30 @@ func (rule *Rule) Ref() Ref {
|
||||
}
|
||||
|
||||
func (rule *Rule) String() string {
|
||||
return rule.stringWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
type toStringOpts struct {
|
||||
regoVersion RegoVersion
|
||||
}
|
||||
|
||||
func (rule *Rule) stringWithOpts(opts toStringOpts) string {
|
||||
buf := []string{}
|
||||
if rule.Default {
|
||||
buf = append(buf, "default")
|
||||
}
|
||||
buf = append(buf, rule.Head.String())
|
||||
buf = append(buf, rule.Head.stringWithOpts(opts))
|
||||
if !rule.Default {
|
||||
switch opts.regoVersion {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, "if")
|
||||
}
|
||||
buf = append(buf, "{")
|
||||
buf = append(buf, rule.Body.String())
|
||||
buf = append(buf, "}")
|
||||
}
|
||||
if rule.Else != nil {
|
||||
buf = append(buf, rule.Else.elseString())
|
||||
buf = append(buf, rule.Else.elseString(opts))
|
||||
}
|
||||
return strings.Join(buf, " ")
|
||||
}
|
||||
@@ -824,7 +850,7 @@ func (rule *Rule) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func (rule *Rule) elseString() string {
|
||||
func (rule *Rule) elseString(opts toStringOpts) string {
|
||||
var buf []string
|
||||
|
||||
buf = append(buf, "else")
|
||||
@@ -835,12 +861,17 @@ func (rule *Rule) elseString() string {
|
||||
buf = append(buf, value.String())
|
||||
}
|
||||
|
||||
switch opts.regoVersion {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, "if")
|
||||
}
|
||||
|
||||
buf = append(buf, "{")
|
||||
buf = append(buf, rule.Body.String())
|
||||
buf = append(buf, "}")
|
||||
|
||||
if rule.Else != nil {
|
||||
buf = append(buf, rule.Else.elseString())
|
||||
buf = append(buf, rule.Else.elseString(opts))
|
||||
}
|
||||
|
||||
return strings.Join(buf, " ")
|
||||
@@ -1000,16 +1031,28 @@ func (head *Head) Equal(other *Head) bool {
|
||||
}
|
||||
|
||||
func (head *Head) String() string {
|
||||
return head.stringWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
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:
|
||||
buf.WriteRune('[')
|
||||
buf.WriteString(head.Key.String())
|
||||
buf.WriteRune(']')
|
||||
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 {
|
||||
@@ -1018,7 +1061,7 @@ func (head *Head) String() string {
|
||||
buf.WriteString(" = ")
|
||||
}
|
||||
buf.WriteString(head.Value.String())
|
||||
} else if head.Name == "" && head.Key != nil {
|
||||
} else if !containsAdded && head.Name == "" && head.Key != nil {
|
||||
buf.WriteString(" contains ")
|
||||
buf.WriteString(head.Key.String())
|
||||
}
|
||||
|
||||
+6
-3
@@ -50,9 +50,12 @@ func checkRootDocumentOverrides(node interface{}) Errors {
|
||||
|
||||
WalkExprs(node, func(expr *Expr) bool {
|
||||
if expr.IsAssignment() {
|
||||
name := expr.Operand(0).String()
|
||||
if RootDocumentRefs.Contains(RefTerm(VarTerm(name))) {
|
||||
errors = append(errors, NewError(CompileErr, expr.Location, "variables must not shadow %v (use a different variable name)", name))
|
||||
// assign() can be called directly, so we need to assert its given first operand exists before checking its name.
|
||||
if nameOp := expr.Operand(0); nameOp != nil {
|
||||
name := nameOp.String()
|
||||
if RootDocumentRefs.Contains(RefTerm(VarTerm(name))) {
|
||||
errors = append(errors, NewError(CompileErr, expr.Location, "variables must not shadow %v (use a different variable name)", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
+7
-1
@@ -200,7 +200,10 @@ func (m Manifest) Equal(other Manifest) bool {
|
||||
if m.RegoVersion != nil && other.RegoVersion != nil && *m.RegoVersion != *other.RegoVersion {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(m.FileRegoVersions, other.FileRegoVersions) {
|
||||
|
||||
// If both are nil, or both are empty, we consider them equal.
|
||||
if !(len(m.FileRegoVersions) == 0 && len(other.FileRegoVersions) == 0) &&
|
||||
!reflect.DeepEqual(m.FileRegoVersions, other.FileRegoVersions) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1092,6 +1095,9 @@ func (b *Bundle) FormatModulesForRegoVersion(version ast.RegoVersion, preserveMo
|
||||
opts := format.Opts{}
|
||||
if preserveModuleRegoVersion {
|
||||
opts.RegoVersion = module.Parsed.RegoVersion()
|
||||
opts.ParserOptions = &ast.ParserOptions{
|
||||
RegoVersion: opts.RegoVersion,
|
||||
}
|
||||
} else {
|
||||
opts.RegoVersion = version
|
||||
}
|
||||
|
||||
+4843
File diff suppressed because it is too large
Load Diff
+89
-31
@@ -52,7 +52,7 @@ func SourceWithOpts(filename string, src []byte, opts Opts) ([]byte, error) {
|
||||
} else {
|
||||
if opts.RegoVersion == ast.RegoV1 {
|
||||
// If the rego version is V1, we need to parse it as such, to allow for future keywords not being imported.
|
||||
// Otherwise, we'll default to RegoV0
|
||||
// Otherwise, we'll default to the default rego-version.
|
||||
parserOpts.RegoVersion = ast.RegoV1
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,16 @@ func MustAst(x interface{}) []byte {
|
||||
return bs
|
||||
}
|
||||
|
||||
// MustAstWithOpts is a helper function to format a Rego AST element. If any errors
|
||||
// occurs this function will panic. This is mostly used for test
|
||||
func MustAstWithOpts(x interface{}, opts Opts) []byte {
|
||||
bs, err := AstWithOpts(x, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// Ast formats a Rego AST element. If the passed value is not a valid AST
|
||||
// element, Ast returns nil and an error. If AST nodes are missing locations
|
||||
// an arbitrary location will be used.
|
||||
@@ -116,7 +126,16 @@ type fmtOpts struct {
|
||||
// than if they don't.
|
||||
refHeads bool
|
||||
|
||||
regoV1 bool
|
||||
regoV1 bool
|
||||
futureKeywords []string
|
||||
}
|
||||
|
||||
func (o fmtOpts) keywords() []string {
|
||||
if o.regoV1 {
|
||||
return ast.KeywordsV1[:]
|
||||
}
|
||||
kws := ast.KeywordsV0[:]
|
||||
return append(kws, o.futureKeywords...)
|
||||
}
|
||||
|
||||
func AstWithOpts(x interface{}, opts Opts) ([]byte, error) {
|
||||
@@ -161,6 +180,10 @@ func AstWithOpts(x interface{}, opts Opts) ([]byte, error) {
|
||||
}
|
||||
|
||||
case *ast.Import:
|
||||
if kw, ok := future.WhichFutureKeyword(n); ok {
|
||||
o.futureKeywords = append(o.futureKeywords, kw)
|
||||
}
|
||||
|
||||
switch {
|
||||
case isRegoV1Compatible(n):
|
||||
o.contains = true
|
||||
@@ -190,8 +213,9 @@ func AstWithOpts(x interface{}, opts Opts) ([]byte, error) {
|
||||
})
|
||||
|
||||
w := &writer{
|
||||
indent: "\t",
|
||||
errs: make([]*ast.Error, 0),
|
||||
indent: "\t",
|
||||
errs: make([]*ast.Error, 0),
|
||||
fmtOpts: o,
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
@@ -209,18 +233,17 @@ func AstWithOpts(x interface{}, opts Opts) ([]byte, error) {
|
||||
x.Imports = ensureFutureKeywordImport(x.Imports, kw)
|
||||
}
|
||||
}
|
||||
w.writeModule(x, o)
|
||||
w.writeModule(x)
|
||||
case *ast.Package:
|
||||
w.writePackage(x, nil)
|
||||
case *ast.Import:
|
||||
w.writeImports([]*ast.Import{x}, nil)
|
||||
case *ast.Rule:
|
||||
w.writeRule(x, false /* isElse */, o, nil)
|
||||
w.writeRule(x, false /* isElse */, nil)
|
||||
case *ast.Head:
|
||||
w.writeHead(x,
|
||||
false, // isDefault
|
||||
false, // isExpandedConst
|
||||
o,
|
||||
nil)
|
||||
case ast.Body:
|
||||
w.writeBody(x, nil)
|
||||
@@ -292,9 +315,10 @@ type writer struct {
|
||||
beforeEnd *ast.Comment
|
||||
delay bool
|
||||
errs ast.Errors
|
||||
fmtOpts fmtOpts
|
||||
}
|
||||
|
||||
func (w *writer) writeModule(module *ast.Module, o fmtOpts) {
|
||||
func (w *writer) writeModule(module *ast.Module) {
|
||||
var pkg *ast.Package
|
||||
var others []interface{}
|
||||
var comments []*ast.Comment
|
||||
@@ -332,7 +356,7 @@ func (w *writer) writeModule(module *ast.Module, o fmtOpts) {
|
||||
imports, others = gatherImports(others)
|
||||
comments = w.writeImports(imports, comments)
|
||||
rules, others = gatherRules(others)
|
||||
comments = w.writeRules(rules, o, comments)
|
||||
comments = w.writeRules(rules, comments)
|
||||
}
|
||||
|
||||
for i, c := range comments {
|
||||
@@ -355,7 +379,15 @@ func (w *writer) writePackage(pkg *ast.Package, comments []*ast.Comment) []*ast.
|
||||
comments = w.insertComments(comments, pkg.Location)
|
||||
|
||||
w.startLine()
|
||||
w.write(pkg.String())
|
||||
|
||||
// Omit head as all packages have the DefaultRootDocument prepended at parse time.
|
||||
path := make(ast.Ref, len(pkg.Path)-1)
|
||||
path[0] = ast.VarTerm(string(pkg.Path[1].Value.(ast.String)))
|
||||
copy(path[1:], pkg.Path[2:])
|
||||
|
||||
w.write("package ")
|
||||
w.writeRef(path)
|
||||
|
||||
w.blankLine()
|
||||
|
||||
return comments
|
||||
@@ -370,16 +402,16 @@ func (w *writer) writeComments(comments []*ast.Comment) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) writeRules(rules []*ast.Rule, o fmtOpts, comments []*ast.Comment) []*ast.Comment {
|
||||
func (w *writer) writeRules(rules []*ast.Rule, comments []*ast.Comment) []*ast.Comment {
|
||||
for _, rule := range rules {
|
||||
comments = w.insertComments(comments, rule.Location)
|
||||
comments = w.writeRule(rule, false, o, comments)
|
||||
comments = w.writeRule(rule, false, comments)
|
||||
w.blankLine()
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func (w *writer) writeRule(rule *ast.Rule, isElse bool, o fmtOpts, comments []*ast.Comment) []*ast.Comment {
|
||||
func (w *writer) writeRule(rule *ast.Rule, isElse bool, comments []*ast.Comment) []*ast.Comment {
|
||||
if rule == nil {
|
||||
return comments
|
||||
}
|
||||
@@ -398,17 +430,17 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, o fmtOpts, comments []*a
|
||||
// pretend that the rule has no body in this case.
|
||||
isExpandedConst := rule.Body.Equal(ast.NewBody(ast.NewExpr(ast.BooleanTerm(true)))) && rule.Else == nil
|
||||
|
||||
comments = w.writeHead(rule.Head, rule.Default, isExpandedConst, o, comments)
|
||||
comments = w.writeHead(rule.Head, rule.Default, isExpandedConst, comments)
|
||||
|
||||
// this excludes partial sets UNLESS `contains` is used
|
||||
partialSetException := o.contains || rule.Head.Value != nil
|
||||
partialSetException := w.fmtOpts.contains || rule.Head.Value != nil
|
||||
|
||||
if len(rule.Body) == 0 || isExpandedConst {
|
||||
w.endLine()
|
||||
return comments
|
||||
}
|
||||
|
||||
if (o.regoV1 || o.ifs) && partialSetException {
|
||||
if (w.fmtOpts.regoV1 || w.fmtOpts.ifs) && partialSetException {
|
||||
w.write(" if")
|
||||
if len(rule.Body) == 1 {
|
||||
if rule.Body[0].Location.Row == rule.Head.Location.Row {
|
||||
@@ -416,7 +448,7 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, o fmtOpts, comments []*a
|
||||
comments = w.writeExpr(rule.Body[0], comments)
|
||||
w.endLine()
|
||||
if rule.Else != nil {
|
||||
comments = w.writeElse(rule, o, comments)
|
||||
comments = w.writeElse(rule, comments)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
@@ -444,12 +476,12 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, o fmtOpts, comments []*a
|
||||
w.startLine()
|
||||
w.write("}")
|
||||
if rule.Else != nil {
|
||||
comments = w.writeElse(rule, o, comments)
|
||||
comments = w.writeElse(rule, comments)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func (w *writer) writeElse(rule *ast.Rule, o fmtOpts, comments []*ast.Comment) []*ast.Comment {
|
||||
func (w *writer) writeElse(rule *ast.Rule, comments []*ast.Comment) []*ast.Comment {
|
||||
// If there was nothing else on the line before the "else" starts
|
||||
// then preserve this style of else block, otherwise it will be
|
||||
// started as an "inline" else eg:
|
||||
@@ -511,16 +543,16 @@ func (w *writer) writeElse(rule *ast.Rule, o fmtOpts, comments []*ast.Comment) [
|
||||
rule.Else.Head.Value.Location = rule.Else.Head.Location
|
||||
}
|
||||
|
||||
return w.writeRule(rule.Else, true, o, comments)
|
||||
return w.writeRule(rule.Else, true, comments)
|
||||
}
|
||||
|
||||
func (w *writer) writeHead(head *ast.Head, isDefault, isExpandedConst bool, o fmtOpts, comments []*ast.Comment) []*ast.Comment {
|
||||
func (w *writer) writeHead(head *ast.Head, isDefault, isExpandedConst bool, comments []*ast.Comment) []*ast.Comment {
|
||||
ref := head.Ref()
|
||||
if head.Key != nil && head.Value == nil && !head.HasDynamicRef() {
|
||||
ref = ref.GroundPrefix()
|
||||
}
|
||||
if o.refHeads || len(ref) == 1 {
|
||||
w.write(ref.String())
|
||||
if w.fmtOpts.refHeads || len(ref) == 1 {
|
||||
w.writeRef(ref)
|
||||
} else {
|
||||
w.write(ref[0].String())
|
||||
w.write("[")
|
||||
@@ -538,7 +570,7 @@ func (w *writer) writeHead(head *ast.Head, isDefault, isExpandedConst bool, o fm
|
||||
w.write(")")
|
||||
}
|
||||
if head.Key != nil {
|
||||
if o.contains && head.Value == nil {
|
||||
if w.fmtOpts.contains && head.Value == nil {
|
||||
w.write(" contains ")
|
||||
comments = w.writeTerm(head.Key, comments)
|
||||
} else if head.Value == nil { // no `if` for p[x] notation
|
||||
@@ -556,10 +588,9 @@ func (w *writer) writeHead(head *ast.Head, isDefault, isExpandedConst bool, o fm
|
||||
// * a.b -> a contains "b"
|
||||
// * a.b.c -> a.b.c := true
|
||||
// * a.b.c.d -> a.b.c.d := true
|
||||
isRegoV1RefConst := o.regoV1 && isExpandedConst && head.Key == nil && len(head.Args) == 0
|
||||
isRegoV1RefConst := w.fmtOpts.regoV1 && isExpandedConst && head.Key == nil && len(head.Args) == 0
|
||||
|
||||
if len(head.Args) > 0 &&
|
||||
head.Location == head.Value.Location &&
|
||||
if head.Location == head.Value.Location &&
|
||||
head.Name != "else" &&
|
||||
ast.Compare(head.Value, ast.BooleanTerm(true)) == 0 &&
|
||||
!isRegoV1RefConst {
|
||||
@@ -569,7 +600,7 @@ func (w *writer) writeHead(head *ast.Head, isDefault, isExpandedConst bool, o fm
|
||||
return comments
|
||||
}
|
||||
|
||||
if head.Assign || o.regoV1 {
|
||||
if head.Assign || w.fmtOpts.regoV1 {
|
||||
// preserve assignment operator, and enforce it if formatting for Rego v1
|
||||
w.write(" := ")
|
||||
} else {
|
||||
@@ -847,7 +878,7 @@ var varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
|
||||
|
||||
func (w *writer) writeRefStringPath(s ast.String) {
|
||||
str := string(s)
|
||||
if varRegexp.MatchString(str) && !ast.IsKeyword(str) {
|
||||
if varRegexp.MatchString(str) && !ast.IsInKeywords(str, w.fmtOpts.keywords()) {
|
||||
w.write("." + str)
|
||||
} else {
|
||||
w.writeBracketed(s.String())
|
||||
@@ -1058,7 +1089,7 @@ func (w *writer) writeImports(imports []*ast.Import, comments []*ast.Comment) []
|
||||
})
|
||||
for _, i := range group {
|
||||
w.startLine()
|
||||
w.write(i.String())
|
||||
w.writeImport(i)
|
||||
if c, ok := m[i]; ok {
|
||||
w.write(" " + c.String())
|
||||
}
|
||||
@@ -1070,6 +1101,28 @@ func (w *writer) writeImports(imports []*ast.Import, comments []*ast.Comment) []
|
||||
return comments
|
||||
}
|
||||
|
||||
func (w *writer) writeImport(imp *ast.Import) {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
|
||||
buf := []string{"import"}
|
||||
|
||||
if _, ok := future.WhichFutureKeyword(imp); ok {
|
||||
// We don't want to wrap future.keywords imports in parens, so we create a new writer that doesn't
|
||||
w2 := writer{
|
||||
buf: bytes.Buffer{},
|
||||
}
|
||||
w2.writeRef(path)
|
||||
buf = append(buf, w2.buf.String())
|
||||
} else {
|
||||
buf = append(buf, path.String())
|
||||
}
|
||||
|
||||
if len(imp.Alias) > 0 {
|
||||
buf = append(buf, "as "+imp.Alias.String())
|
||||
}
|
||||
w.write(strings.Join(buf, " "))
|
||||
}
|
||||
|
||||
type entryWriter func(interface{}, []*ast.Comment) []*ast.Comment
|
||||
|
||||
func (w *writer) writeIterable(elements []interface{}, last *ast.Location, close *ast.Location, comments []*ast.Comment, fn entryWriter) []*ast.Comment {
|
||||
@@ -1496,7 +1549,12 @@ func ensureFutureKeywordImport(imps []*ast.Import, kw string) []*ast.Import {
|
||||
}
|
||||
}
|
||||
imp := &ast.Import{
|
||||
Path: ast.MustParseTerm("future.keywords." + kw),
|
||||
// NOTE: This is a hack to not error on the ref containing a keyword already present in v1.
|
||||
// A cleaner solution would be to instead allow refs to contain keyword terms.
|
||||
// E.g. in v1, `import future.keywords["in"]` is valid, but `import future.keywords.in` is not
|
||||
// as it contains a reserved keyword.
|
||||
Path: ast.MustParseTerm("future.keywords[\"" + kw + "\"]"),
|
||||
//Path: ast.MustParseTerm("future.keywords." + kw),
|
||||
}
|
||||
imp.Location = defaultLocation(imp)
|
||||
return append(imps, imp)
|
||||
|
||||
+5
-5
@@ -749,12 +749,12 @@ opa_set_get,opa_value_hash
|
||||
opa_set_get,opa_value_compare
|
||||
opa_number_try_int,opa_atoi64
|
||||
opa_number_try_int,opa_abort
|
||||
opa_value_get,opa_abort
|
||||
opa_value_get,opa_atoi64
|
||||
opa_value_get,opa_value_hash
|
||||
opa_value_get,opa_value_compare
|
||||
opa_value_compare_number,opa_atoi64
|
||||
opa_value_get,opa_abort
|
||||
opa_value_compare_number,opa_abort
|
||||
opa_value_compare_number,opa_atoi64
|
||||
opa_value_compare_number,opa_number_to_bf
|
||||
opa_value_compare_number,mpd_qcmp
|
||||
opa_value_compare_number,mpd_del
|
||||
@@ -779,10 +779,10 @@ opa_value_compare_set,opa_value_compare_set
|
||||
opa_value_compare_set,opa_abort
|
||||
opa_number_hash,opa_atof64
|
||||
opa_number_hash,opa_abort
|
||||
opa_value_iter,opa_abort
|
||||
opa_value_iter,opa_atoi64
|
||||
opa_value_iter,opa_value_hash
|
||||
opa_value_iter,opa_value_compare
|
||||
opa_value_iter,opa_abort
|
||||
opa_object_keys,opa_malloc
|
||||
opa_object_keys,opa_free
|
||||
opa_object_keys,opa_value_compare
|
||||
@@ -817,7 +817,6 @@ opa_value_merge,opa_malloc
|
||||
opa_value_merge,opa_value_get
|
||||
opa_value_merge,__opa_object_insert
|
||||
opa_value_merge,opa_value_merge
|
||||
opa_value_merge,opa_abort
|
||||
opa_value_merge,opa_atoi64
|
||||
opa_value_merge,opa_value_hash
|
||||
opa_value_merge,opa_value_compare_number
|
||||
@@ -825,6 +824,7 @@ opa_value_merge,opa_strncmp
|
||||
opa_value_merge,opa_value_compare
|
||||
opa_value_merge,opa_value_compare_object
|
||||
opa_value_merge,opa_value_compare_set
|
||||
opa_value_merge,opa_abort
|
||||
__opa_object_insert,opa_value_hash
|
||||
__opa_object_insert,opa_value_compare
|
||||
__opa_object_insert,__opa_value_free
|
||||
@@ -904,8 +904,8 @@ opa_value_remove_path,opa_value_get
|
||||
opa_value_remove_path,opa_object_remove
|
||||
opa_lookup,opa_value_get
|
||||
opa_lookup,opa_value_iter
|
||||
opa_lookup,opa_atoi64
|
||||
opa_lookup,opa_abort
|
||||
opa_lookup,opa_atoi64
|
||||
opa_mapping_init,opa_json_parse
|
||||
opa_mapping_lookup,opa_lookup
|
||||
node::re2\28std::__1::basic_string<char\2c\20std::__1::char_traits<char>\2c\20std::__1::allocator<char>\20>\20const&\29,std::__1::basic_string<char\2c\20std::__1::char_traits<char>\2c\20std::__1::allocator<char>\20>::assign\28char\20const*\29
|
||||
|
||||
|
BIN
Binary file not shown.
+12
@@ -35,3 +35,15 @@ func IsFutureKeyword(imp *ast.Import, kw string) bool {
|
||||
path[1].Equal(ast.StringTerm("keywords")) &&
|
||||
path[2].Equal(ast.StringTerm(kw))
|
||||
}
|
||||
|
||||
func WhichFutureKeyword(imp *ast.Import) (string, bool) {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
if len(path) == 3 &&
|
||||
ast.FutureRootDocument.Equal(path[0]) &&
|
||||
path[1].Equal(ast.StringTerm("keywords")) {
|
||||
if str, ok := path[2].Value.(ast.String); ok {
|
||||
return string(str), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
+10
-9
@@ -18,13 +18,14 @@ type Result struct {
|
||||
|
||||
// EvalOpts define options for performing an evaluation.
|
||||
type EvalOpts struct {
|
||||
Input *interface{}
|
||||
Metrics metrics.Metrics
|
||||
Entrypoint int32
|
||||
Time time.Time
|
||||
Seed io.Reader
|
||||
InterQueryBuiltinCache cache.InterQueryCache
|
||||
NDBuiltinCache builtins.NDBCache
|
||||
PrintHook print.Hook
|
||||
Capabilities *ast.Capabilities
|
||||
Input *interface{}
|
||||
Metrics metrics.Metrics
|
||||
Entrypoint int32
|
||||
Time time.Time
|
||||
Seed io.Reader
|
||||
InterQueryBuiltinCache cache.InterQueryCache
|
||||
InterQueryBuiltinValueCache cache.InterQueryValueCache
|
||||
NDBuiltinCache builtins.NDBCache
|
||||
PrintHook print.Hook
|
||||
Capabilities *ast.Capabilities
|
||||
}
|
||||
|
||||
+1
-1
@@ -576,7 +576,7 @@ func (m *Manager) Labels() map[string]string {
|
||||
return m.Config.Labels
|
||||
}
|
||||
|
||||
// InterQueryBuiltinCacheConfig returns the configuration for the inter-query cache.
|
||||
// InterQueryBuiltinCacheConfig returns the configuration for the inter-query caches.
|
||||
func (m *Manager) InterQueryBuiltinCacheConfig() *cache.Config {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
+21
-7
@@ -30,10 +30,11 @@ const (
|
||||
ec2DefaultTokenPath = "http://169.254.169.254/latest/api/token"
|
||||
|
||||
// ref. https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html
|
||||
ecsDefaultCredServicePath = "http://169.254.170.2"
|
||||
ecsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||
ecsFullPathEnvVar = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
|
||||
ecsAuthorizationTokenEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
|
||||
ecsDefaultCredServicePath = "http://169.254.170.2"
|
||||
ecsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||
ecsFullPathEnvVar = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
|
||||
ecsAuthorizationTokenEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
|
||||
ecsAuthorizationTokenFileEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"
|
||||
|
||||
// ref. https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html
|
||||
stsDefaultDomain = "amazonaws.com"
|
||||
@@ -277,9 +278,22 @@ func (cs *awsMetadataCredentialService) refreshFromService(ctx context.Context)
|
||||
// if using the AWS_CONTAINER_CREDENTIALS_FULL_URI variable, we need to associate the token
|
||||
// to the request
|
||||
if _, useFullPath := os.LookupEnv(ecsFullPathEnvVar); useFullPath {
|
||||
token, tokenExists := os.LookupEnv(ecsAuthorizationTokenEnvVar)
|
||||
if !tokenExists {
|
||||
return errors.New("unable to get ECS metadata authorization token")
|
||||
var token string
|
||||
tokenFilePath, tokenFilePathExists := os.LookupEnv(ecsAuthorizationTokenFileEnvVar)
|
||||
|
||||
if tokenFilePathExists {
|
||||
tokenBytes, err := os.ReadFile(tokenFilePath)
|
||||
if err != nil {
|
||||
return errors.New("failed to read ECS metadata authorization token from file: " + err.Error())
|
||||
}
|
||||
token = string(tokenBytes)
|
||||
// If token doesn't exist as a file check if it exists as an environment variable
|
||||
} else {
|
||||
var tokenExists bool
|
||||
token, tokenExists = os.LookupEnv(ecsAuthorizationTokenEnvVar)
|
||||
if !tokenExists {
|
||||
return errors.New("unable to get ECS metadata authorization token")
|
||||
}
|
||||
}
|
||||
req.Header.Set("Authorization", token)
|
||||
}
|
||||
|
||||
+112
-90
@@ -99,32 +99,33 @@ type preparedQuery struct {
|
||||
// EvalContext defines the set of options allowed to be set at evaluation
|
||||
// time. Any other options will need to be set on a new Rego object.
|
||||
type EvalContext struct {
|
||||
hasInput bool
|
||||
time time.Time
|
||||
seed io.Reader
|
||||
rawInput *interface{}
|
||||
parsedInput ast.Value
|
||||
metrics metrics.Metrics
|
||||
txn storage.Transaction
|
||||
instrument bool
|
||||
instrumentation *topdown.Instrumentation
|
||||
partialNamespace string
|
||||
queryTracers []topdown.QueryTracer
|
||||
compiledQuery compiledQuery
|
||||
unknowns []string
|
||||
disableInlining []ast.Ref
|
||||
parsedUnknowns []*ast.Term
|
||||
indexing bool
|
||||
earlyExit bool
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
resolvers []refResolver
|
||||
sortSets bool
|
||||
copyMaps bool
|
||||
printHook print.Hook
|
||||
capabilities *ast.Capabilities
|
||||
strictBuiltinErrors bool
|
||||
virtualCache topdown.VirtualCache
|
||||
hasInput bool
|
||||
time time.Time
|
||||
seed io.Reader
|
||||
rawInput *interface{}
|
||||
parsedInput ast.Value
|
||||
metrics metrics.Metrics
|
||||
txn storage.Transaction
|
||||
instrument bool
|
||||
instrumentation *topdown.Instrumentation
|
||||
partialNamespace string
|
||||
queryTracers []topdown.QueryTracer
|
||||
compiledQuery compiledQuery
|
||||
unknowns []string
|
||||
disableInlining []ast.Ref
|
||||
parsedUnknowns []*ast.Term
|
||||
indexing bool
|
||||
earlyExit bool
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
interQueryBuiltinValueCache cache.InterQueryValueCache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
resolvers []refResolver
|
||||
sortSets bool
|
||||
copyMaps bool
|
||||
printHook print.Hook
|
||||
capabilities *ast.Capabilities
|
||||
strictBuiltinErrors bool
|
||||
virtualCache topdown.VirtualCache
|
||||
}
|
||||
|
||||
func (e *EvalContext) RawInput() *interface{} {
|
||||
@@ -147,6 +148,10 @@ func (e *EvalContext) InterQueryBuiltinCache() cache.InterQueryCache {
|
||||
return e.interQueryBuiltinCache
|
||||
}
|
||||
|
||||
func (e *EvalContext) InterQueryBuiltinValueCache() cache.InterQueryValueCache {
|
||||
return e.interQueryBuiltinValueCache
|
||||
}
|
||||
|
||||
func (e *EvalContext) PrintHook() print.Hook {
|
||||
return e.printHook
|
||||
}
|
||||
@@ -307,6 +312,14 @@ func EvalInterQueryBuiltinCache(c cache.InterQueryCache) EvalOption {
|
||||
}
|
||||
}
|
||||
|
||||
// EvalInterQueryBuiltinValueCache sets the inter-query value cache that built-in functions can utilize
|
||||
// during evaluation.
|
||||
func EvalInterQueryBuiltinValueCache(c cache.InterQueryValueCache) EvalOption {
|
||||
return func(e *EvalContext) {
|
||||
e.interQueryBuiltinValueCache = c
|
||||
}
|
||||
}
|
||||
|
||||
// EvalNDBuiltinCache sets the non-deterministic builtin cache that built-in functions can
|
||||
// use during evaluation.
|
||||
func EvalNDBuiltinCache(c builtins.NDBCache) EvalOption {
|
||||
@@ -546,64 +559,65 @@ type loadPaths struct {
|
||||
|
||||
// Rego constructs a query and can be evaluated to obtain results.
|
||||
type Rego struct {
|
||||
query string
|
||||
parsedQuery ast.Body
|
||||
compiledQueries map[queryType]compiledQuery
|
||||
pkg string
|
||||
parsedPackage *ast.Package
|
||||
imports []string
|
||||
parsedImports []*ast.Import
|
||||
rawInput *interface{}
|
||||
parsedInput ast.Value
|
||||
unknowns []string
|
||||
parsedUnknowns []*ast.Term
|
||||
disableInlining []string
|
||||
shallowInlining bool
|
||||
skipPartialNamespace bool
|
||||
partialNamespace string
|
||||
modules []rawModule
|
||||
parsedModules map[string]*ast.Module
|
||||
compiler *ast.Compiler
|
||||
store storage.Store
|
||||
ownStore bool
|
||||
txn storage.Transaction
|
||||
metrics metrics.Metrics
|
||||
queryTracers []topdown.QueryTracer
|
||||
tracebuf *topdown.BufferTracer
|
||||
trace bool
|
||||
instrumentation *topdown.Instrumentation
|
||||
instrument bool
|
||||
capture map[*ast.Expr]ast.Var // map exprs to generated capture vars
|
||||
termVarID int
|
||||
dump io.Writer
|
||||
runtime *ast.Term
|
||||
time time.Time
|
||||
seed io.Reader
|
||||
capabilities *ast.Capabilities
|
||||
builtinDecls map[string]*ast.Builtin
|
||||
builtinFuncs map[string]*topdown.Builtin
|
||||
unsafeBuiltins map[string]struct{}
|
||||
loadPaths loadPaths
|
||||
bundlePaths []string
|
||||
bundles map[string]*bundle.Bundle
|
||||
skipBundleVerification bool
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
strictBuiltinErrors bool
|
||||
builtinErrorList *[]topdown.Error
|
||||
resolvers []refResolver
|
||||
schemaSet *ast.SchemaSet
|
||||
target string // target type (wasm, rego, etc.)
|
||||
opa opa.EvalEngine
|
||||
generateJSON func(*ast.Term, *EvalContext) (interface{}, error)
|
||||
printHook print.Hook
|
||||
enablePrintStatements bool
|
||||
distributedTacingOpts tracing.Options
|
||||
strict bool
|
||||
pluginMgr *plugins.Manager
|
||||
plugins []TargetPlugin
|
||||
targetPrepState TargetPluginEval
|
||||
regoVersion ast.RegoVersion
|
||||
query string
|
||||
parsedQuery ast.Body
|
||||
compiledQueries map[queryType]compiledQuery
|
||||
pkg string
|
||||
parsedPackage *ast.Package
|
||||
imports []string
|
||||
parsedImports []*ast.Import
|
||||
rawInput *interface{}
|
||||
parsedInput ast.Value
|
||||
unknowns []string
|
||||
parsedUnknowns []*ast.Term
|
||||
disableInlining []string
|
||||
shallowInlining bool
|
||||
skipPartialNamespace bool
|
||||
partialNamespace string
|
||||
modules []rawModule
|
||||
parsedModules map[string]*ast.Module
|
||||
compiler *ast.Compiler
|
||||
store storage.Store
|
||||
ownStore bool
|
||||
txn storage.Transaction
|
||||
metrics metrics.Metrics
|
||||
queryTracers []topdown.QueryTracer
|
||||
tracebuf *topdown.BufferTracer
|
||||
trace bool
|
||||
instrumentation *topdown.Instrumentation
|
||||
instrument bool
|
||||
capture map[*ast.Expr]ast.Var // map exprs to generated capture vars
|
||||
termVarID int
|
||||
dump io.Writer
|
||||
runtime *ast.Term
|
||||
time time.Time
|
||||
seed io.Reader
|
||||
capabilities *ast.Capabilities
|
||||
builtinDecls map[string]*ast.Builtin
|
||||
builtinFuncs map[string]*topdown.Builtin
|
||||
unsafeBuiltins map[string]struct{}
|
||||
loadPaths loadPaths
|
||||
bundlePaths []string
|
||||
bundles map[string]*bundle.Bundle
|
||||
skipBundleVerification bool
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
interQueryBuiltinValueCache cache.InterQueryValueCache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
strictBuiltinErrors bool
|
||||
builtinErrorList *[]topdown.Error
|
||||
resolvers []refResolver
|
||||
schemaSet *ast.SchemaSet
|
||||
target string // target type (wasm, rego, etc.)
|
||||
opa opa.EvalEngine
|
||||
generateJSON func(*ast.Term, *EvalContext) (interface{}, error)
|
||||
printHook print.Hook
|
||||
enablePrintStatements bool
|
||||
distributedTacingOpts tracing.Options
|
||||
strict bool
|
||||
pluginMgr *plugins.Manager
|
||||
plugins []TargetPlugin
|
||||
targetPrepState TargetPluginEval
|
||||
regoVersion ast.RegoVersion
|
||||
}
|
||||
|
||||
// Function represents a built-in function that is callable in Rego.
|
||||
@@ -1114,6 +1128,14 @@ func InterQueryBuiltinCache(c cache.InterQueryCache) func(r *Rego) {
|
||||
}
|
||||
}
|
||||
|
||||
// InterQueryBuiltinValueCache sets the inter-query value cache that built-in functions can utilize
|
||||
// during evaluation.
|
||||
func InterQueryBuiltinValueCache(c cache.InterQueryValueCache) func(r *Rego) {
|
||||
return func(r *Rego) {
|
||||
r.interQueryBuiltinValueCache = c
|
||||
}
|
||||
}
|
||||
|
||||
// NDBuiltinCache sets the non-deterministic builtins cache.
|
||||
func NDBuiltinCache(c builtins.NDBCache) func(r *Rego) {
|
||||
return func(r *Rego) {
|
||||
@@ -1309,6 +1331,7 @@ func (r *Rego) Eval(ctx context.Context) (ResultSet, error) {
|
||||
EvalInstrument(r.instrument),
|
||||
EvalTime(r.time),
|
||||
EvalInterQueryBuiltinCache(r.interQueryBuiltinCache),
|
||||
EvalInterQueryBuiltinValueCache(r.interQueryBuiltinValueCache),
|
||||
EvalSeed(r.seed),
|
||||
}
|
||||
|
||||
@@ -1386,6 +1409,7 @@ func (r *Rego) Partial(ctx context.Context) (*PartialQueries, error) {
|
||||
EvalMetrics(r.metrics),
|
||||
EvalInstrument(r.instrument),
|
||||
EvalInterQueryBuiltinCache(r.interQueryBuiltinCache),
|
||||
EvalInterQueryBuiltinValueCache(r.interQueryBuiltinValueCache),
|
||||
}
|
||||
|
||||
if r.ndBuiltinCache != nil {
|
||||
@@ -1943,6 +1967,7 @@ func (r *Rego) parseQuery(queryImports []*ast.Import, m metrics.Metrics) (ast.Bo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
popts.RegoVersion = r.regoVersion
|
||||
popts, err = parserOptionsFromRegoVersionImport(queryImports, popts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2106,6 +2131,7 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
|
||||
WithIndexing(ectx.indexing).
|
||||
WithEarlyExit(ectx.earlyExit).
|
||||
WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache).
|
||||
WithInterQueryBuiltinValueCache(ectx.interQueryBuiltinValueCache).
|
||||
WithStrictBuiltinErrors(r.strictBuiltinErrors).
|
||||
WithBuiltinErrorList(r.builtinErrorList).
|
||||
WithSeed(ectx.seed).
|
||||
@@ -2164,7 +2190,6 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
|
||||
}
|
||||
|
||||
func (r *Rego) evalWasm(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
|
||||
|
||||
input := ectx.rawInput
|
||||
if ectx.parsedInput != nil {
|
||||
i := interface{}(ectx.parsedInput)
|
||||
@@ -2393,6 +2418,7 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries,
|
||||
WithSkipPartialNamespace(r.skipPartialNamespace).
|
||||
WithShallowInlining(r.shallowInlining).
|
||||
WithInterQueryBuiltinCache(ectx.interQueryBuiltinCache).
|
||||
WithInterQueryBuiltinValueCache(ectx.interQueryBuiltinValueCache).
|
||||
WithStrictBuiltinErrors(ectx.strictBuiltinErrors).
|
||||
WithSeed(ectx.seed).
|
||||
WithPrintHook(ectx.printHook)
|
||||
@@ -2431,14 +2457,10 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the target rego-version is v0, and the rego.v1 import is available, then we attempt to apply it to support modules.
|
||||
if r.regoVersion == ast.RegoV0 && (r.capabilities == nil || r.capabilities.ContainsFeature(ast.FeatureRegoV1Import)) {
|
||||
// If the target rego-version in v0, and the rego.v1 import is available, then we attempt to apply it to support modules.
|
||||
|
||||
for i, mod := range support {
|
||||
if mod.RegoVersion() != ast.RegoV0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// We can't apply the RegoV0CompatV1 version to the support module if it contains rules or vars that
|
||||
// conflict with future keywords.
|
||||
applyRegoVersion := true
|
||||
|
||||
+20
-19
@@ -35,25 +35,26 @@ type (
|
||||
// BuiltinContext contains context from the evaluator that may be used by
|
||||
// built-in functions.
|
||||
BuiltinContext struct {
|
||||
Context context.Context // request context that was passed when query started
|
||||
Metrics metrics.Metrics // metrics registry for recording built-in specific metrics
|
||||
Seed io.Reader // randomization source
|
||||
Time *ast.Term // wall clock time
|
||||
Cancel Cancel // atomic value that signals evaluation to halt
|
||||
Runtime *ast.Term // runtime information on the OPA instance
|
||||
Cache builtins.Cache // built-in function state cache
|
||||
InterQueryBuiltinCache cache.InterQueryCache // cross-query built-in function state cache
|
||||
NDBuiltinCache builtins.NDBCache // cache for non-deterministic built-in state
|
||||
Location *ast.Location // location of built-in call
|
||||
Tracers []Tracer // Deprecated: Use QueryTracers instead
|
||||
QueryTracers []QueryTracer // tracer objects for trace() built-in function
|
||||
TraceEnabled bool // indicates whether tracing is enabled for the evaluation
|
||||
QueryID uint64 // identifies query being evaluated
|
||||
ParentID uint64 // identifies parent of query being evaluated
|
||||
PrintHook print.Hook // provides callback function to use for printing
|
||||
DistributedTracingOpts tracing.Options // options to be used by distributed tracing.
|
||||
rand *rand.Rand // randomization source for non-security-sensitive operations
|
||||
Capabilities *ast.Capabilities
|
||||
Context context.Context // request context that was passed when query started
|
||||
Metrics metrics.Metrics // metrics registry for recording built-in specific metrics
|
||||
Seed io.Reader // randomization source
|
||||
Time *ast.Term // wall clock time
|
||||
Cancel Cancel // atomic value that signals evaluation to halt
|
||||
Runtime *ast.Term // runtime information on the OPA instance
|
||||
Cache builtins.Cache // built-in function state cache
|
||||
InterQueryBuiltinCache cache.InterQueryCache // cross-query built-in function state cache
|
||||
InterQueryBuiltinValueCache cache.InterQueryValueCache // cross-query built-in function state value cache. this cache is useful for scenarios where the entry size cannot be calculated
|
||||
NDBuiltinCache builtins.NDBCache // cache for non-deterministic built-in state
|
||||
Location *ast.Location // location of built-in call
|
||||
Tracers []Tracer // Deprecated: Use QueryTracers instead
|
||||
QueryTracers []QueryTracer // tracer objects for trace() built-in function
|
||||
TraceEnabled bool // indicates whether tracing is enabled for the evaluation
|
||||
QueryID uint64 // identifies query being evaluated
|
||||
ParentID uint64 // identifies parent of query being evaluated
|
||||
PrintHook print.Hook // provides callback function to use for printing
|
||||
DistributedTracingOpts tracing.Options // options to be used by distributed tracing.
|
||||
rand *rand.Rand // randomization source for non-security-sensitive operations
|
||||
Capabilities *ast.Capabilities
|
||||
}
|
||||
|
||||
// BuiltinFunc defines an interface for implementing built-in functions.
|
||||
|
||||
+106
-3
@@ -18,14 +18,22 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultInterQueryBuiltinValueCacheSize = int(0) // unlimited
|
||||
defaultMaxSizeBytes = int64(0) // unlimited
|
||||
defaultForcedEvictionThresholdPercentage = int64(100) // trigger at max_size_bytes
|
||||
defaultStaleEntryEvictionPeriodSeconds = int64(0) // never
|
||||
)
|
||||
|
||||
// Config represents the configuration of the inter-query cache.
|
||||
// Config represents the configuration for the inter-query builtin cache.
|
||||
type Config struct {
|
||||
InterQueryBuiltinCache InterQueryBuiltinCacheConfig `json:"inter_query_builtin_cache"`
|
||||
InterQueryBuiltinCache InterQueryBuiltinCacheConfig `json:"inter_query_builtin_cache"`
|
||||
InterQueryBuiltinValueCache InterQueryBuiltinValueCacheConfig `json:"inter_query_builtin_value_cache"`
|
||||
}
|
||||
|
||||
// InterQueryBuiltinValueCacheConfig represents the configuration of the inter-query value cache that built-in functions can utilize.
|
||||
// MaxNumEntries - max number of cache entries
|
||||
type InterQueryBuiltinValueCacheConfig struct {
|
||||
MaxNumEntries *int `json:"max_num_entries,omitempty"`
|
||||
}
|
||||
|
||||
// InterQueryBuiltinCacheConfig represents the configuration of the inter-query cache that built-in functions can utilize.
|
||||
@@ -47,7 +55,12 @@ func ParseCachingConfig(raw []byte) (*Config, error) {
|
||||
*threshold = defaultForcedEvictionThresholdPercentage
|
||||
period := new(int64)
|
||||
*period = defaultStaleEntryEvictionPeriodSeconds
|
||||
return &Config{InterQueryBuiltinCache: InterQueryBuiltinCacheConfig{MaxSizeBytes: maxSize, ForcedEvictionThresholdPercentage: threshold, StaleEntryEvictionPeriodSeconds: period}}, nil
|
||||
|
||||
maxInterQueryBuiltinValueCacheSize := new(int)
|
||||
*maxInterQueryBuiltinValueCacheSize = defaultInterQueryBuiltinValueCacheSize
|
||||
|
||||
return &Config{InterQueryBuiltinCache: InterQueryBuiltinCacheConfig{MaxSizeBytes: maxSize, ForcedEvictionThresholdPercentage: threshold, StaleEntryEvictionPeriodSeconds: period},
|
||||
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{MaxNumEntries: maxInterQueryBuiltinValueCacheSize}}, nil
|
||||
}
|
||||
|
||||
var config Config
|
||||
@@ -89,6 +102,18 @@ func (c *Config) validateAndInjectDefaults() error {
|
||||
return fmt.Errorf("invalid stale_entry_eviction_period_seconds %v", period)
|
||||
}
|
||||
}
|
||||
|
||||
if c.InterQueryBuiltinValueCache.MaxNumEntries == nil {
|
||||
maxSize := new(int)
|
||||
*maxSize = defaultInterQueryBuiltinValueCacheSize
|
||||
c.InterQueryBuiltinValueCache.MaxNumEntries = maxSize
|
||||
} else {
|
||||
numEntries := *c.InterQueryBuiltinValueCache.MaxNumEntries
|
||||
if numEntries < 0 {
|
||||
return fmt.Errorf("invalid max_num_entries %v", numEntries)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -301,3 +326,81 @@ func (c *cache) cleanStaleValues() (dropped int) {
|
||||
}
|
||||
return dropped
|
||||
}
|
||||
|
||||
type InterQueryValueCache interface {
|
||||
Get(key ast.Value) (value any, found bool)
|
||||
Insert(key ast.Value, value any) int
|
||||
Delete(key ast.Value)
|
||||
UpdateConfig(config *Config)
|
||||
}
|
||||
|
||||
type interQueryValueCache struct {
|
||||
items map[string]any
|
||||
config *Config
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
// Get returns the value in the cache for k.
|
||||
func (c *interQueryValueCache) Get(k ast.Value) (any, bool) {
|
||||
c.mtx.RLock()
|
||||
defer c.mtx.RUnlock()
|
||||
value, ok := c.items[k.String()]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// Insert inserts a key k into the cache with value v.
|
||||
func (c *interQueryValueCache) Insert(k ast.Value, v any) (dropped int) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
maxEntries := c.maxNumEntries()
|
||||
if maxEntries > 0 {
|
||||
if len(c.items) >= maxEntries {
|
||||
itemsToRemove := len(c.items) - maxEntries + 1
|
||||
|
||||
// Delete a (semi-)random key to make room for the new one.
|
||||
for k := range c.items {
|
||||
delete(c.items, k)
|
||||
dropped++
|
||||
|
||||
if itemsToRemove == dropped {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.items[k.String()] = v
|
||||
return dropped
|
||||
}
|
||||
|
||||
// Delete deletes the value in the cache for k.
|
||||
func (c *interQueryValueCache) Delete(k ast.Value) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
delete(c.items, k.String())
|
||||
}
|
||||
|
||||
// UpdateConfig updates the cache config.
|
||||
func (c *interQueryValueCache) UpdateConfig(config *Config) {
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.config = config
|
||||
}
|
||||
|
||||
func (c *interQueryValueCache) maxNumEntries() int {
|
||||
if c.config == nil {
|
||||
return defaultInterQueryBuiltinValueCacheSize
|
||||
}
|
||||
return *c.config.InterQueryBuiltinValueCache.MaxNumEntries
|
||||
}
|
||||
|
||||
func NewInterQueryValueCache(_ context.Context, config *Config) InterQueryValueCache {
|
||||
return &interQueryValueCache{
|
||||
items: map[string]any{},
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
+68
-66
@@ -58,55 +58,56 @@ func (ee deferredEarlyExitError) Error() string {
|
||||
}
|
||||
|
||||
type eval struct {
|
||||
ctx context.Context
|
||||
metrics metrics.Metrics
|
||||
seed io.Reader
|
||||
time *ast.Term
|
||||
queryID uint64
|
||||
queryIDFact *queryIDFactory
|
||||
parent *eval
|
||||
caller *eval
|
||||
cancel Cancel
|
||||
query ast.Body
|
||||
queryCompiler ast.QueryCompiler
|
||||
index int
|
||||
indexing bool
|
||||
earlyExit bool
|
||||
bindings *bindings
|
||||
store storage.Store
|
||||
baseCache *baseCache
|
||||
txn storage.Transaction
|
||||
compiler *ast.Compiler
|
||||
input *ast.Term
|
||||
data *ast.Term
|
||||
external *resolverTrie
|
||||
targetStack *refStack
|
||||
tracers []QueryTracer
|
||||
traceEnabled bool
|
||||
traceLastLocation *ast.Location // Last location of a trace event.
|
||||
plugTraceVars bool
|
||||
instr *Instrumentation
|
||||
builtins map[string]*Builtin
|
||||
builtinCache builtins.Cache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
functionMocks *functionMocksStack
|
||||
virtualCache VirtualCache
|
||||
comprehensionCache *comprehensionCache
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
saveSet *saveSet
|
||||
saveStack *saveStack
|
||||
saveSupport *saveSupport
|
||||
saveNamespace *ast.Term
|
||||
skipSaveNamespace bool
|
||||
inliningControl *inliningControl
|
||||
genvarprefix string
|
||||
genvarid int
|
||||
runtime *ast.Term
|
||||
builtinErrors *builtinErrors
|
||||
printHook print.Hook
|
||||
tracingOpts tracing.Options
|
||||
findOne bool
|
||||
strictObjects bool
|
||||
ctx context.Context
|
||||
metrics metrics.Metrics
|
||||
seed io.Reader
|
||||
time *ast.Term
|
||||
queryID uint64
|
||||
queryIDFact *queryIDFactory
|
||||
parent *eval
|
||||
caller *eval
|
||||
cancel Cancel
|
||||
query ast.Body
|
||||
queryCompiler ast.QueryCompiler
|
||||
index int
|
||||
indexing bool
|
||||
earlyExit bool
|
||||
bindings *bindings
|
||||
store storage.Store
|
||||
baseCache *baseCache
|
||||
txn storage.Transaction
|
||||
compiler *ast.Compiler
|
||||
input *ast.Term
|
||||
data *ast.Term
|
||||
external *resolverTrie
|
||||
targetStack *refStack
|
||||
tracers []QueryTracer
|
||||
traceEnabled bool
|
||||
traceLastLocation *ast.Location // Last location of a trace event.
|
||||
plugTraceVars bool
|
||||
instr *Instrumentation
|
||||
builtins map[string]*Builtin
|
||||
builtinCache builtins.Cache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
functionMocks *functionMocksStack
|
||||
virtualCache VirtualCache
|
||||
comprehensionCache *comprehensionCache
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
interQueryBuiltinValueCache cache.InterQueryValueCache
|
||||
saveSet *saveSet
|
||||
saveStack *saveStack
|
||||
saveSupport *saveSupport
|
||||
saveNamespace *ast.Term
|
||||
skipSaveNamespace bool
|
||||
inliningControl *inliningControl
|
||||
genvarprefix string
|
||||
genvarid int
|
||||
runtime *ast.Term
|
||||
builtinErrors *builtinErrors
|
||||
printHook print.Hook
|
||||
tracingOpts tracing.Options
|
||||
findOne bool
|
||||
strictObjects bool
|
||||
}
|
||||
|
||||
func (e *eval) Run(iter evalIterator) error {
|
||||
@@ -817,23 +818,24 @@ func (e *eval) evalCall(terms []*ast.Term, iter unifyIterator) error {
|
||||
}
|
||||
|
||||
bctx := BuiltinContext{
|
||||
Context: e.ctx,
|
||||
Metrics: e.metrics,
|
||||
Seed: e.seed,
|
||||
Time: e.time,
|
||||
Cancel: e.cancel,
|
||||
Runtime: e.runtime,
|
||||
Cache: e.builtinCache,
|
||||
InterQueryBuiltinCache: e.interQueryBuiltinCache,
|
||||
NDBuiltinCache: e.ndBuiltinCache,
|
||||
Location: e.query[e.index].Location,
|
||||
QueryTracers: e.tracers,
|
||||
TraceEnabled: e.traceEnabled,
|
||||
QueryID: e.queryID,
|
||||
ParentID: parentID,
|
||||
PrintHook: e.printHook,
|
||||
DistributedTracingOpts: e.tracingOpts,
|
||||
Capabilities: capabilities,
|
||||
Context: e.ctx,
|
||||
Metrics: e.metrics,
|
||||
Seed: e.seed,
|
||||
Time: e.time,
|
||||
Cancel: e.cancel,
|
||||
Runtime: e.runtime,
|
||||
Cache: e.builtinCache,
|
||||
InterQueryBuiltinCache: e.interQueryBuiltinCache,
|
||||
InterQueryBuiltinValueCache: e.interQueryBuiltinValueCache,
|
||||
NDBuiltinCache: e.ndBuiltinCache,
|
||||
Location: e.query[e.index].Location,
|
||||
QueryTracers: e.tracers,
|
||||
TraceEnabled: e.traceEnabled,
|
||||
QueryID: e.queryID,
|
||||
ParentID: parentID,
|
||||
PrintHook: e.printHook,
|
||||
DistributedTracingOpts: e.tracingOpts,
|
||||
Capabilities: capabilities,
|
||||
}
|
||||
|
||||
eval := evalBuiltin{
|
||||
|
||||
+31
-3
@@ -11,11 +11,12 @@ import (
|
||||
)
|
||||
|
||||
const globCacheMaxSize = 100
|
||||
const globInterQueryValueCacheHits = "rego_builtin_glob_interquery_value_cache_hits"
|
||||
|
||||
var globCacheLock = sync.Mutex{}
|
||||
var globCache map[string]glob.Glob
|
||||
|
||||
func builtinGlobMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
func builtinGlobMatch(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
pattern, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,14 +51,41 @@ func builtinGlobMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter
|
||||
}
|
||||
id := builder.String()
|
||||
|
||||
m, err := globCompileAndMatch(id, string(pattern), string(match), delimiters)
|
||||
m, err := globCompileAndMatch(bctx, id, string(pattern), string(match), delimiters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return iter(ast.BooleanTerm(m))
|
||||
}
|
||||
|
||||
func globCompileAndMatch(id, pattern, match string, delimiters []rune) (bool, error) {
|
||||
func globCompileAndMatch(bctx BuiltinContext, id, pattern, match string, delimiters []rune) (bool, error) {
|
||||
|
||||
if bctx.InterQueryBuiltinValueCache != nil {
|
||||
val, ok := bctx.InterQueryBuiltinValueCache.Get(ast.String(id))
|
||||
if ok {
|
||||
pat, valid := val.(glob.Glob)
|
||||
if !valid {
|
||||
// The cache key may exist for a different value type (eg. regex).
|
||||
// In this case, we calculate the glob and return the result w/o updating the cache.
|
||||
var err error
|
||||
if pat, err = glob.Compile(pattern, delimiters...); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return pat.Match(match), nil
|
||||
}
|
||||
bctx.Metrics.Counter(globInterQueryValueCacheHits).Incr()
|
||||
out := pat.Match(match)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
res, err := glob.Compile(pattern, delimiters...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bctx.InterQueryBuiltinValueCache.Insert(ast.String(id), res)
|
||||
return res.Match(match), nil
|
||||
}
|
||||
|
||||
globCacheLock.Lock()
|
||||
defer globCacheLock.Unlock()
|
||||
p, ok := globCache[id]
|
||||
|
||||
+2
-3
@@ -168,6 +168,7 @@ func generateRaiseErrorResult(err error) *ast.Term {
|
||||
func getHTTPResponse(bctx BuiltinContext, req ast.Object) (*ast.Term, error) {
|
||||
|
||||
bctx.Metrics.Timer(httpSendLatencyMetricKey).Start()
|
||||
defer bctx.Metrics.Timer(httpSendLatencyMetricKey).Stop()
|
||||
|
||||
key, err := getKeyFromRequest(req)
|
||||
if err != nil {
|
||||
@@ -199,8 +200,6 @@ func getHTTPResponse(bctx BuiltinContext, req ast.Object) (*ast.Term, error) {
|
||||
}
|
||||
}
|
||||
|
||||
bctx.Metrics.Timer(httpSendLatencyMetricKey).Stop()
|
||||
|
||||
return ast.NewTerm(resp), nil
|
||||
}
|
||||
|
||||
@@ -474,7 +473,7 @@ func createHTTPRequest(bctx BuiltinContext, obj ast.Object) (*http.Request, *htt
|
||||
}
|
||||
body = bytes.NewBuffer(bodyValBytes)
|
||||
case "raw_body":
|
||||
rawBody = bytes.NewBuffer([]byte(strVal))
|
||||
rawBody = bytes.NewBufferString(strVal)
|
||||
case "tls_use_system_certs":
|
||||
tempTLSUseSystemCerts, err := strconv.ParseBool(obj.Get(val).String())
|
||||
if err != nil {
|
||||
|
||||
+110
-101
@@ -27,38 +27,39 @@ type QueryResult map[ast.Var]*ast.Term
|
||||
|
||||
// Query provides a configurable interface for performing query evaluation.
|
||||
type Query struct {
|
||||
seed io.Reader
|
||||
time time.Time
|
||||
cancel Cancel
|
||||
query ast.Body
|
||||
queryCompiler ast.QueryCompiler
|
||||
compiler *ast.Compiler
|
||||
store storage.Store
|
||||
txn storage.Transaction
|
||||
input *ast.Term
|
||||
external *resolverTrie
|
||||
tracers []QueryTracer
|
||||
plugTraceVars bool
|
||||
unknowns []*ast.Term
|
||||
partialNamespace string
|
||||
skipSaveNamespace bool
|
||||
metrics metrics.Metrics
|
||||
instr *Instrumentation
|
||||
disableInlining []ast.Ref
|
||||
shallowInlining bool
|
||||
genvarprefix string
|
||||
runtime *ast.Term
|
||||
builtins map[string]*Builtin
|
||||
indexing bool
|
||||
earlyExit bool
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
strictBuiltinErrors bool
|
||||
builtinErrorList *[]Error
|
||||
strictObjects bool
|
||||
printHook print.Hook
|
||||
tracingOpts tracing.Options
|
||||
virtualCache VirtualCache
|
||||
seed io.Reader
|
||||
time time.Time
|
||||
cancel Cancel
|
||||
query ast.Body
|
||||
queryCompiler ast.QueryCompiler
|
||||
compiler *ast.Compiler
|
||||
store storage.Store
|
||||
txn storage.Transaction
|
||||
input *ast.Term
|
||||
external *resolverTrie
|
||||
tracers []QueryTracer
|
||||
plugTraceVars bool
|
||||
unknowns []*ast.Term
|
||||
partialNamespace string
|
||||
skipSaveNamespace bool
|
||||
metrics metrics.Metrics
|
||||
instr *Instrumentation
|
||||
disableInlining []ast.Ref
|
||||
shallowInlining bool
|
||||
genvarprefix string
|
||||
runtime *ast.Term
|
||||
builtins map[string]*Builtin
|
||||
indexing bool
|
||||
earlyExit bool
|
||||
interQueryBuiltinCache cache.InterQueryCache
|
||||
interQueryBuiltinValueCache cache.InterQueryValueCache
|
||||
ndBuiltinCache builtins.NDBCache
|
||||
strictBuiltinErrors bool
|
||||
builtinErrorList *[]Error
|
||||
strictObjects bool
|
||||
printHook print.Hook
|
||||
tracingOpts tracing.Options
|
||||
virtualCache VirtualCache
|
||||
}
|
||||
|
||||
// Builtin represents a built-in function that queries can call.
|
||||
@@ -246,6 +247,12 @@ func (q *Query) WithInterQueryBuiltinCache(c cache.InterQueryCache) *Query {
|
||||
return q
|
||||
}
|
||||
|
||||
// WithInterQueryBuiltinValueCache sets the inter-query value cache that built-in functions can utilize.
|
||||
func (q *Query) WithInterQueryBuiltinValueCache(c cache.InterQueryValueCache) *Query {
|
||||
q.interQueryBuiltinValueCache = c
|
||||
return q
|
||||
}
|
||||
|
||||
// WithNDBuiltinCache sets the non-deterministic builtin cache.
|
||||
func (q *Query) WithNDBuiltinCache(c builtins.NDBCache) *Query {
|
||||
q.ndBuiltinCache = c
|
||||
@@ -331,39 +338,40 @@ func (q *Query) PartialRun(ctx context.Context) (partials []ast.Body, support []
|
||||
}
|
||||
|
||||
e := &eval{
|
||||
ctx: ctx,
|
||||
metrics: q.metrics,
|
||||
seed: q.seed,
|
||||
time: ast.NumberTerm(int64ToJSONNumber(q.time.UnixNano())),
|
||||
cancel: q.cancel,
|
||||
query: q.query,
|
||||
queryCompiler: q.queryCompiler,
|
||||
queryIDFact: f,
|
||||
queryID: f.Next(),
|
||||
bindings: b,
|
||||
compiler: q.compiler,
|
||||
store: q.store,
|
||||
baseCache: newBaseCache(),
|
||||
targetStack: newRefStack(),
|
||||
txn: q.txn,
|
||||
input: q.input,
|
||||
external: q.external,
|
||||
tracers: q.tracers,
|
||||
traceEnabled: len(q.tracers) > 0,
|
||||
plugTraceVars: q.plugTraceVars,
|
||||
instr: q.instr,
|
||||
builtins: q.builtins,
|
||||
builtinCache: builtins.Cache{},
|
||||
functionMocks: newFunctionMocksStack(),
|
||||
interQueryBuiltinCache: q.interQueryBuiltinCache,
|
||||
ndBuiltinCache: q.ndBuiltinCache,
|
||||
virtualCache: vc,
|
||||
comprehensionCache: newComprehensionCache(),
|
||||
saveSet: newSaveSet(q.unknowns, b, q.instr),
|
||||
saveStack: newSaveStack(),
|
||||
saveSupport: newSaveSupport(),
|
||||
saveNamespace: ast.StringTerm(q.partialNamespace),
|
||||
skipSaveNamespace: q.skipSaveNamespace,
|
||||
ctx: ctx,
|
||||
metrics: q.metrics,
|
||||
seed: q.seed,
|
||||
time: ast.NumberTerm(int64ToJSONNumber(q.time.UnixNano())),
|
||||
cancel: q.cancel,
|
||||
query: q.query,
|
||||
queryCompiler: q.queryCompiler,
|
||||
queryIDFact: f,
|
||||
queryID: f.Next(),
|
||||
bindings: b,
|
||||
compiler: q.compiler,
|
||||
store: q.store,
|
||||
baseCache: newBaseCache(),
|
||||
targetStack: newRefStack(),
|
||||
txn: q.txn,
|
||||
input: q.input,
|
||||
external: q.external,
|
||||
tracers: q.tracers,
|
||||
traceEnabled: len(q.tracers) > 0,
|
||||
plugTraceVars: q.plugTraceVars,
|
||||
instr: q.instr,
|
||||
builtins: q.builtins,
|
||||
builtinCache: builtins.Cache{},
|
||||
functionMocks: newFunctionMocksStack(),
|
||||
interQueryBuiltinCache: q.interQueryBuiltinCache,
|
||||
interQueryBuiltinValueCache: q.interQueryBuiltinValueCache,
|
||||
ndBuiltinCache: q.ndBuiltinCache,
|
||||
virtualCache: vc,
|
||||
comprehensionCache: newComprehensionCache(),
|
||||
saveSet: newSaveSet(q.unknowns, b, q.instr),
|
||||
saveStack: newSaveStack(),
|
||||
saveSupport: newSaveSupport(),
|
||||
saveNamespace: ast.StringTerm(q.partialNamespace),
|
||||
skipSaveNamespace: q.skipSaveNamespace,
|
||||
inliningControl: &inliningControl{
|
||||
shallow: q.shallowInlining,
|
||||
},
|
||||
@@ -516,42 +524,43 @@ func (q *Query) Iter(ctx context.Context, iter func(QueryResult) error) error {
|
||||
}
|
||||
|
||||
e := &eval{
|
||||
ctx: ctx,
|
||||
metrics: q.metrics,
|
||||
seed: q.seed,
|
||||
time: ast.NumberTerm(int64ToJSONNumber(q.time.UnixNano())),
|
||||
cancel: q.cancel,
|
||||
query: q.query,
|
||||
queryCompiler: q.queryCompiler,
|
||||
queryIDFact: f,
|
||||
queryID: f.Next(),
|
||||
bindings: newBindings(0, q.instr),
|
||||
compiler: q.compiler,
|
||||
store: q.store,
|
||||
baseCache: newBaseCache(),
|
||||
targetStack: newRefStack(),
|
||||
txn: q.txn,
|
||||
input: q.input,
|
||||
external: q.external,
|
||||
tracers: q.tracers,
|
||||
traceEnabled: len(q.tracers) > 0,
|
||||
plugTraceVars: q.plugTraceVars,
|
||||
instr: q.instr,
|
||||
builtins: q.builtins,
|
||||
builtinCache: builtins.Cache{},
|
||||
functionMocks: newFunctionMocksStack(),
|
||||
interQueryBuiltinCache: q.interQueryBuiltinCache,
|
||||
ndBuiltinCache: q.ndBuiltinCache,
|
||||
virtualCache: vc,
|
||||
comprehensionCache: newComprehensionCache(),
|
||||
genvarprefix: q.genvarprefix,
|
||||
runtime: q.runtime,
|
||||
indexing: q.indexing,
|
||||
earlyExit: q.earlyExit,
|
||||
builtinErrors: &builtinErrors{},
|
||||
printHook: q.printHook,
|
||||
tracingOpts: q.tracingOpts,
|
||||
strictObjects: q.strictObjects,
|
||||
ctx: ctx,
|
||||
metrics: q.metrics,
|
||||
seed: q.seed,
|
||||
time: ast.NumberTerm(int64ToJSONNumber(q.time.UnixNano())),
|
||||
cancel: q.cancel,
|
||||
query: q.query,
|
||||
queryCompiler: q.queryCompiler,
|
||||
queryIDFact: f,
|
||||
queryID: f.Next(),
|
||||
bindings: newBindings(0, q.instr),
|
||||
compiler: q.compiler,
|
||||
store: q.store,
|
||||
baseCache: newBaseCache(),
|
||||
targetStack: newRefStack(),
|
||||
txn: q.txn,
|
||||
input: q.input,
|
||||
external: q.external,
|
||||
tracers: q.tracers,
|
||||
traceEnabled: len(q.tracers) > 0,
|
||||
plugTraceVars: q.plugTraceVars,
|
||||
instr: q.instr,
|
||||
builtins: q.builtins,
|
||||
builtinCache: builtins.Cache{},
|
||||
functionMocks: newFunctionMocksStack(),
|
||||
interQueryBuiltinCache: q.interQueryBuiltinCache,
|
||||
interQueryBuiltinValueCache: q.interQueryBuiltinValueCache,
|
||||
ndBuiltinCache: q.ndBuiltinCache,
|
||||
virtualCache: vc,
|
||||
comprehensionCache: newComprehensionCache(),
|
||||
genvarprefix: q.genvarprefix,
|
||||
runtime: q.runtime,
|
||||
indexing: q.indexing,
|
||||
earlyExit: q.earlyExit,
|
||||
builtinErrors: &builtinErrors{},
|
||||
printHook: q.printHook,
|
||||
tracingOpts: q.tracingOpts,
|
||||
strictObjects: q.strictObjects,
|
||||
}
|
||||
e.caller = e
|
||||
q.metrics.Timer(metrics.RegoQueryEval).Start()
|
||||
|
||||
+34
-11
@@ -16,6 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const regexCacheMaxSize = 100
|
||||
const regexInterQueryValueCacheHits = "rego_builtin_regex_interquery_value_cache_hits"
|
||||
|
||||
var regexpCacheLock = sync.Mutex{}
|
||||
var regexpCache map[string]*regexp.Regexp
|
||||
@@ -35,7 +36,7 @@ func builtinRegexIsValid(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
return iter(ast.BooleanTerm(true))
|
||||
}
|
||||
|
||||
func builtinRegexMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
func builtinRegexMatch(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
s1, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -44,7 +45,7 @@ func builtinRegexMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re, err := getRegexp(string(s1))
|
||||
re, err := getRegexp(bctx, string(s1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +82,7 @@ func builtinRegexMatchTemplate(_ BuiltinContext, operands []*ast.Term, iter func
|
||||
return iter(ast.BooleanTerm(re.MatchString(string(match))))
|
||||
}
|
||||
|
||||
func builtinRegexSplit(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
func builtinRegexSplit(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
s1, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -90,7 +91,7 @@ func builtinRegexSplit(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re, err := getRegexp(string(s1))
|
||||
re, err := getRegexp(bctx, string(s1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,7 +104,29 @@ func builtinRegexSplit(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
return iter(ast.NewTerm(ast.NewArray(arr...)))
|
||||
}
|
||||
|
||||
func getRegexp(pat string) (*regexp.Regexp, error) {
|
||||
func getRegexp(bctx BuiltinContext, pat string) (*regexp.Regexp, error) {
|
||||
if bctx.InterQueryBuiltinValueCache != nil {
|
||||
val, ok := bctx.InterQueryBuiltinValueCache.Get(ast.String(pat))
|
||||
if ok {
|
||||
res, valid := val.(*regexp.Regexp)
|
||||
if !valid {
|
||||
// The cache key may exist for a different value type (eg. glob).
|
||||
// In this case, we calculate the regex and return the result w/o updating the cache.
|
||||
return regexp.Compile(pat)
|
||||
}
|
||||
|
||||
bctx.Metrics.Counter(regexInterQueryValueCacheHits).Incr()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(pat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bctx.InterQueryBuiltinValueCache.Insert(ast.String(pat), re)
|
||||
return re, nil
|
||||
}
|
||||
|
||||
regexpCacheLock.Lock()
|
||||
defer regexpCacheLock.Unlock()
|
||||
re, ok := regexpCache[pat]
|
||||
@@ -156,7 +179,7 @@ func builtinGlobsMatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
return iter(ast.BooleanTerm(ne))
|
||||
}
|
||||
|
||||
func builtinRegexFind(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
func builtinRegexFind(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
s1, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -169,7 +192,7 @@ func builtinRegexFind(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re, err := getRegexp(string(s1))
|
||||
re, err := getRegexp(bctx, string(s1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -182,7 +205,7 @@ func builtinRegexFind(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter
|
||||
return iter(ast.NewTerm(ast.NewArray(arr...)))
|
||||
}
|
||||
|
||||
func builtinRegexFindAllStringSubmatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
func builtinRegexFindAllStringSubmatch(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
s1, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -196,7 +219,7 @@ func builtinRegexFindAllStringSubmatch(_ BuiltinContext, operands []*ast.Term, i
|
||||
return err
|
||||
}
|
||||
|
||||
re, err := getRegexp(string(s1))
|
||||
re, err := getRegexp(bctx, string(s1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -214,7 +237,7 @@ func builtinRegexFindAllStringSubmatch(_ BuiltinContext, operands []*ast.Term, i
|
||||
return iter(ast.NewTerm(ast.NewArray(outer...)))
|
||||
}
|
||||
|
||||
func builtinRegexReplace(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
func builtinRegexReplace(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
base, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -230,7 +253,7 @@ func builtinRegexReplace(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
return err
|
||||
}
|
||||
|
||||
re, err := getRegexp(string(pattern))
|
||||
re, err := getRegexp(bctx, string(pattern))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+4
@@ -114,6 +114,10 @@ func Reference(x interface{}) *interface{} {
|
||||
|
||||
// Unmarshal decodes a YAML, JSON or JSON extension value into the specified type.
|
||||
func Unmarshal(bs []byte, v interface{}) error {
|
||||
if len(bs) > 2 && bs[0] == 0xef && bs[1] == 0xbb && bs[2] == 0xbf {
|
||||
bs = bs[3:] // Strip UTF-8 BOM, see https://www.rfc-editor.org/rfc/rfc8259#section-8.1
|
||||
}
|
||||
|
||||
if json.Valid(bs) {
|
||||
return unmarshalJSON(bs, v, false)
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// Version is the canonical version of OPA.
|
||||
var Version = "0.68.0"
|
||||
var Version = "0.69.0"
|
||||
|
||||
// GoVersion is the version of Go this was built with
|
||||
var GoVersion = runtime.Version()
|
||||
|
||||
Reference in New Issue
Block a user