build(deps): bump github.com/open-policy-agent/opa from 0.51.0 to 0.59.0

Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.51.0 to 0.59.0.
- [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/v0.51.0...v0.59.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-12-05 09:47:11 +01:00
committed by Ralf Haferkamp
parent a6a6c22c14
commit 1f069c7c00
197 changed files with 73803 additions and 3024 deletions
+15 -11
View File
@@ -11,6 +11,7 @@ import (
"sort"
"strings"
astJSON "github.com/open-policy-agent/opa/ast/json"
"github.com/open-policy-agent/opa/internal/deepcopy"
"github.com/open-policy-agent/opa/util"
)
@@ -39,7 +40,7 @@ type (
comments []*Comment
node Node
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// SchemaAnnotation contains a schema declaration for the document identified by the path.
@@ -76,7 +77,7 @@ type (
Annotations *Annotations `json:"annotations,omitempty"`
Location *Location `json:"location,omitempty"` // The location of the node the annotations are applied to
jsonOptions JSONOptions
jsonOptions astJSON.Options
node Node // The node the annotations are applied to
}
@@ -174,14 +175,17 @@ func (a *Annotations) GetTargetPath() Ref {
case *Package:
return n.Path
case *Rule:
return n.Path()
return n.Ref().GroundPrefix()
default:
return nil
}
}
func (a *Annotations) setJSONOptions(opts JSONOptions) {
func (a *Annotations) setJSONOptions(opts astJSON.Options) {
a.jsonOptions = opts
if a.Location != nil {
a.Location.JSONOptions = opts
}
}
func (a *Annotations) MarshalJSON() ([]byte, error) {
@@ -348,12 +352,12 @@ func compareRelatedResources(a, b []*RelatedResourceAnnotation) int {
}
func compareSchemas(a, b []*SchemaAnnotation) int {
max := len(a)
if len(b) < max {
max = len(b)
maxLen := len(a)
if len(b) < maxLen {
maxLen = len(b)
}
for i := 0; i < max; i++ {
for i := 0; i < maxLen; i++ {
if cmp := a[i].Compare(b[i]); cmp != 0 {
return cmp
}
@@ -715,7 +719,7 @@ func (as *AnnotationSet) add(a *Annotations) *Error {
}
case annotationScopeDocument:
if rule, ok := a.node.(*Rule); ok {
path := rule.Path()
path := rule.Ref().GroundPrefix()
x := as.byPath.get(path)
if x != nil {
return errAnnotationRedeclared(a, x.Value.Location)
@@ -811,7 +815,7 @@ func (as *AnnotationSet) Chain(rule *Rule) AnnotationsRefSet {
// Make sure there is always a leading entry representing the passed rule, even if it has no annotations
refs = append(refs, &AnnotationsRef{
Location: rule.Location,
Path: rule.Path(),
Path: rule.Ref().GroundPrefix(),
node: rule,
})
}
@@ -823,7 +827,7 @@ func (as *AnnotationSet) Chain(rule *Rule) AnnotationsRefSet {
})
}
docAnnots := as.GetDocumentScope(rule.Path())
docAnnots := as.GetDocumentScope(rule.Ref().GroundPrefix())
if docAnnots != nil {
refs = append(refs, NewAnnotationsRef(docAnnots))
}
+105 -4
View File
@@ -133,9 +133,11 @@ var DefaultBuiltins = [...]*Builtin{
TrimSpace,
Sprintf,
StringReverse,
RenderTemplate,
// Numbers
NumbersRange,
NumbersRangeStep,
RandIntn,
// Encoding
@@ -210,10 +212,13 @@ var DefaultBuiltins = [...]*Builtin{
CryptoSha256,
CryptoX509ParseCertificateRequest,
CryptoX509ParseRSAPrivateKey,
CryptoX509ParseKeyPair,
CryptoParsePrivateKeys,
CryptoHmacMd5,
CryptoHmacSha1,
CryptoHmacSha256,
CryptoHmacSha512,
CryptoHmacEqual,
// Graphs
WalkBuiltin,
@@ -282,6 +287,7 @@ var DefaultBuiltins = [...]*Builtin{
// UUIDs
UUIDRFC4122,
UUIDParse,
// SemVers
SemVerIsValid,
@@ -1312,6 +1318,20 @@ var StringReverse = &Builtin{
Categories: stringsCat,
}
var RenderTemplate = &Builtin{
Name: "strings.render_template",
Description: `Renders a templated string with given template variables injected. For a given templated string and key/value mapping, values will be injected into the template where they are referenced by key.
For examples of templating syntax, see https://pkg.go.dev/text/template`,
Decl: types.NewFunction(
types.Args(
types.Named("value", types.S).Description("a templated string"),
types.Named("vars", types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))).Description("a mapping of template variable keys to values"),
),
types.Named("result", types.S).Description("rendered template with template variables injected"),
),
Categories: stringsCat,
}
/**
* Numbers
*/
@@ -1344,6 +1364,23 @@ var NumbersRange = &Builtin{
),
}
var NumbersRangeStep = &Builtin{
Name: "numbers.range_step",
Description: `Returns an array of numbers in the given (inclusive) range incremented by a positive step.
If "a==b", then "range == [a]"; if "a > b", then "range" is in descending order.
If the provided "step" is less then 1, an error will be thrown.
If "b" is not in the range of the provided "step", "b" won't be included in the result.
`,
Decl: types.NewFunction(
types.Args(
types.Named("a", types.N),
types.Named("b", types.N),
types.Named("step", types.N),
),
types.Named("range", types.NewArray(nil, types.N)).Description("the range between `a` and `b` in `step` increments"),
),
}
/**
* Units
*/
@@ -1397,6 +1434,19 @@ var UUIDRFC4122 = &Builtin{
Nondeterministic: true,
}
var UUIDParse = &Builtin{
Name: "uuid.parse",
Description: "Parses the string value as an UUID and returns an object with the well-defined fields of the UUID if valid.",
Categories: nil,
Decl: types.NewFunction(
types.Args(
types.Named("uuid", types.S),
),
types.Named("result", types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))).Description("Properties of UUID if valid (version, variant, etc). Undefined otherwise."),
),
Relation: false,
}
/**
* JSON
*/
@@ -2158,7 +2208,7 @@ var Format = &Builtin{
types.N,
types.NewArray([]types.Type{types.N, types.S}, nil),
types.NewArray([]types.Type{types.N, types.S, types.S}, nil),
)).Description("a number representing the nanoseconds since the epoch (UTC); or a two-element array of the nanoseconds, and a timezone string; or a three-element array of ns, timezone string and a layout string (see golang supported time formats)"),
)).Description("a number representing the nanoseconds since the epoch (UTC); or a two-element array of the nanoseconds, and a timezone string; or a three-element array of ns, timezone string and a layout string or golang defined formatting constant (see golang supported time formats)"),
),
types.Named("formatted timestamp", types.S).Description("the formatted timestamp represented for the nanoseconds since the epoch in the supplied timezone (or UTC)"),
),
@@ -2209,7 +2259,7 @@ var Weekday = &Builtin{
var AddDate = &Builtin{
Name: "time.add_date",
Description: "Returns the nanoseconds since epoch after adding years, months and days to nanoseconds. `undefined` if the result would be outside the valid time range that can fit within an `int64`.",
Description: "Returns the nanoseconds since epoch after adding years, months and days to nanoseconds. Month & day values outside their usual ranges after the operation and will be normalized - for example, October 32 would become November 1. `undefined` if the result would be outside the valid time range that can fit within an `int64`.",
Decl: types.NewFunction(
types.Args(
types.Named("ns", types.N).Description("nanoseconds since the epoch"),
@@ -2288,6 +2338,17 @@ var CryptoX509ParseCertificateRequest = &Builtin{
),
}
var CryptoX509ParseKeyPair = &Builtin{
Name: "crypto.x509.parse_keypair",
Description: "Returns a valid key pair",
Decl: types.NewFunction(
types.Args(
types.Named("cert", types.S).Description("string containing PEM or base64 encoded DER certificates"),
types.Named("pem", types.S).Description("string containing PEM or base64 encoded DER keys"),
),
types.Named("output", types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))).Description("if key pair is valid, returns the tls.certificate(https://pkg.go.dev/crypto/tls#Certificate) as an object. If the key pair is invalid, nil and an error are returned."),
),
}
var CryptoX509ParseRSAPrivateKey = &Builtin{
Name: "crypto.x509.parse_rsa_private_key",
Description: "Returns a JWK for signing a JWT from the given PEM-encoded RSA private key.",
@@ -2299,6 +2360,19 @@ var CryptoX509ParseRSAPrivateKey = &Builtin{
),
}
var CryptoParsePrivateKeys = &Builtin{
Name: "crypto.parse_private_keys",
Description: `Returns zero or more private keys from the given encoded string containing DER certificate data.
If the input is empty, the function will return null. The input string should be a list of one or more concatenated PEM blocks. The whole input of concatenated PEM blocks can optionally be Base64 encoded.`,
Decl: types.NewFunction(
types.Args(
types.Named("keys", types.S).Description("PEM encoded data containing one or more private keys as concatenated blocks. Optionally Base64 encoded."),
),
types.Named("output", types.NewArray(nil, types.NewObject(nil, types.NewDynamicProperty(types.S, types.A)))).Description("parsed private keys represented as objects"),
),
}
var CryptoMd5 = &Builtin{
Name: "crypto.md5",
Description: "Returns a string representing the input string hashed with the MD5 function",
@@ -2380,6 +2454,18 @@ var CryptoHmacSha512 = &Builtin{
),
}
var CryptoHmacEqual = &Builtin{
Name: "crypto.hmac.equal",
Description: "Returns a boolean representing the result of comparing two MACs for equality without leaking timing information.",
Decl: types.NewFunction(
types.Args(
types.Named("mac1", types.S).Description("mac1 to compare"),
types.Named("mac2", types.S).Description("mac2 to compare"),
),
types.Named("result", types.B).Description("`true` if the MACs are equals, `false` otherwise"),
),
}
/**
* Graphs.
*/
@@ -2399,7 +2485,7 @@ var WalkBuiltin = &Builtin{
types.A,
},
nil,
)).Description("pairs of `path` and `value`: `path` is an array representing the pointer to `value` in `x`"),
)).Description("pairs of `path` and `value`: `path` is an array representing the pointer to `value` in `x`. If `path` is assigned a wildcard (`_`), the `walk` function will skip path creation entirely for faster evaluation."),
),
Categories: graphs,
}
@@ -3155,6 +3241,21 @@ func category(cs ...string) []string {
return cs
}
// Minimal returns a shallow copy of b with the descriptions and categories and
// named arguments stripped out.
func (b *Builtin) Minimal() *Builtin {
cpy := *b
fargs := b.Decl.FuncArgs()
if fargs.Variadic != nil {
cpy.Decl = types.NewVariadicFunction(fargs.Args, fargs.Variadic, b.Decl.Result())
} else {
cpy.Decl = types.NewFunction(fargs.Args, b.Decl.Result())
}
cpy.Categories = nil
cpy.Description = ""
return &cpy
}
// IsDeprecated returns true if the Builtin function is deprecated and will be removed in a future release.
func (b *Builtin) IsDeprecated() bool {
return b.deprecated
@@ -3201,7 +3302,7 @@ func (b *Builtin) Ref() Ref {
// IsTargetPos returns true if a variable in the i-th position will be bound by
// evaluating the call expression.
func (b *Builtin) IsTargetPos(i int) bool {
return len(b.Decl.Args()) == i
return len(b.Decl.FuncArgs().Args) == i
}
func init() {
+105 -4
View File
@@ -6,6 +6,8 @@ package ast
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"io"
"os"
@@ -13,27 +15,56 @@ import (
"strings"
caps "github.com/open-policy-agent/opa/capabilities"
"github.com/open-policy-agent/opa/internal/semver"
"github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities"
"github.com/open-policy-agent/opa/util"
)
// VersonIndex contains an index from built-in function name, language feature,
// and future rego keyword to version number. During the build, this is used to
// create an index of the minimum version required for the built-in/feature/kw.
type VersionIndex struct {
Builtins map[string]semver.Version `json:"builtins"`
Features map[string]semver.Version `json:"features"`
Keywords map[string]semver.Version `json:"keywords"`
}
// NOTE(tsandall): this file is generated by internal/cmd/genversionindex/main.go
// and run as part of go:generate. We generate the version index as part of the
// build process because it's relatively expensive to build (it takes ~500ms on
// my machine) and never changes.
//
//go:embed version_index.json
var versionIndexBs []byte
var minVersionIndex = func() VersionIndex {
var vi VersionIndex
err := json.Unmarshal(versionIndexBs, &vi)
if err != nil {
panic(err)
}
return vi
}()
// In the compiler, we used this to check that we're OK working with ref heads.
// If this isn't present, we'll fail. This is to ensure that older versions of
// OPA can work with policies that we're compiling -- if they don't know ref
// heads, they wouldn't be able to parse them.
const FeatureRefHeadStringPrefixes = "rule_head_ref_string_prefixes"
const FeatureRefHeads = "rule_head_refs"
const FeatureRegoV1Import = "rego_v1_import"
// Capabilities defines a structure containing data that describes the capabilities
// or features supported by a particular version of OPA.
type Capabilities struct {
Builtins []*Builtin `json:"builtins"`
FutureKeywords []string `json:"future_keywords"`
WasmABIVersions []WasmABIVersion `json:"wasm_abi_versions"`
Builtins []*Builtin `json:"builtins,omitempty"`
FutureKeywords []string `json:"future_keywords,omitempty"`
WasmABIVersions []WasmABIVersion `json:"wasm_abi_versions,omitempty"`
// Features is a bit of a mixed bag for checking that an older version of OPA
// is able to do what needs to be done.
// TODO(sr): find better words ^^
Features []string `json:"features"`
Features []string `json:"features,omitempty"`
// allow_net is an array of hostnames or IP addresses, that an OPA instance is
// allowed to connect to.
@@ -73,6 +104,8 @@ func CapabilitiesForThisVersion() *Capabilities {
f.Features = []string{
FeatureRefHeadStringPrefixes,
FeatureRefHeads,
FeatureRegoV1Import,
}
return f
@@ -129,3 +162,71 @@ func LoadCapabilitiesVersions() ([]string, error) {
}
return capabilitiesVersions, nil
}
// MinimumCompatibleVersion returns the minimum compatible OPA version based on
// the built-ins, features, and keywords in c.
func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
var maxVersion semver.Version
// this is the oldest OPA release that includes capabilities
if err := maxVersion.Set("0.17.0"); err != nil {
panic("unreachable")
}
for _, bi := range c.Builtins {
v, ok := minVersionIndex.Builtins[bi.Name]
if !ok {
return "", false
}
if v.Compare(maxVersion) > 0 {
maxVersion = v
}
}
for _, kw := range c.FutureKeywords {
v, ok := minVersionIndex.Keywords[kw]
if !ok {
return "", false
}
if v.Compare(maxVersion) > 0 {
maxVersion = v
}
}
for _, feat := range c.Features {
v, ok := minVersionIndex.Features[feat]
if !ok {
return "", false
}
if v.Compare(maxVersion) > 0 {
maxVersion = v
}
}
return maxVersion.String(), true
}
func (c *Capabilities) ContainsFeature(feature string) bool {
for _, f := range c.Features {
if f == feature {
return true
}
}
return false
}
// addBuiltinSorted inserts a built-in into c in sorted order. An existing built-in with the same name
// will be overwritten.
func (c *Capabilities) addBuiltinSorted(bi *Builtin) {
i := sort.Search(len(c.Builtins), func(x int) bool {
return c.Builtins[x].Name >= bi.Name
})
if i < len(c.Builtins) && bi.Name == c.Builtins[i].Name {
c.Builtins[i] = bi
return
}
c.Builtins = append(c.Builtins, nil)
copy(c.Builtins[i+1:], c.Builtins[i:])
c.Builtins[i] = bi
}
+79 -38
View File
@@ -24,6 +24,8 @@ type exprChecker func(*TypeEnv, *Expr) *Error
// accumulated on the typeChecker so that a single run can report multiple
// issues.
type typeChecker struct {
builtins map[string]*Builtin
required *Capabilities
errs Errors
exprCheckers map[string]exprChecker
varRewriter varRewriter
@@ -60,6 +62,16 @@ func (tc *typeChecker) copy() *typeChecker {
WithInputType(tc.input)
}
func (tc *typeChecker) WithRequiredCapabilities(c *Capabilities) *typeChecker {
tc.required = c
return tc
}
func (tc *typeChecker) WithBuiltins(builtins map[string]*Builtin) *typeChecker {
tc.builtins = builtins
return tc
}
func (tc *typeChecker) WithSchemaSet(ss *SchemaSet) *typeChecker {
tc.ss = ss
return tc
@@ -177,27 +189,26 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
env = env.wrap()
if schemaAnnots := getRuleAnnotation(as, rule); schemaAnnots != nil {
for _, schemaAnnot := range schemaAnnots {
ref, refType, err := processAnnotation(tc.ss, schemaAnnot, rule, tc.allowNet)
schemaAnnots := getRuleAnnotation(as, rule)
for _, schemaAnnot := range schemaAnnots {
ref, refType, err := processAnnotation(tc.ss, schemaAnnot, rule, tc.allowNet)
if err != nil {
tc.err([]*Error{err})
continue
}
if ref == nil && refType == nil {
continue
}
prefixRef, t := getPrefix(env, ref)
if t == nil || len(prefixRef) == len(ref) {
env.tree.Put(ref, refType)
} else {
newType, err := override(ref[len(prefixRef):], t, refType, rule)
if err != nil {
tc.err([]*Error{err})
continue
}
if ref == nil && refType == nil {
continue
}
prefixRef, t := getPrefix(env, ref)
if t == nil || len(prefixRef) == len(ref) {
env.tree.Put(ref, refType)
} else {
newType, err := override(ref[len(prefixRef):], t, refType, rule)
if err != nil {
tc.err([]*Error{err})
continue
}
env.tree.Put(prefixRef, newType)
}
env.tree.Put(prefixRef, newType)
}
}
@@ -232,47 +243,66 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
f := types.NewFunction(args, cpy.Get(rule.Head.Value))
// Union with existing.
exist := env.tree.Get(path)
tpe = types.Or(exist, f)
tpe = f
} else {
switch rule.Head.RuleKind() {
case SingleValue:
typeV := cpy.Get(rule.Head.Value)
if last := path[len(path)-1]; !last.IsGround() {
// e.g. store object[string: whatever] at data.p.q.r, not data.p.q.r[x]
if !path.IsGround() {
// e.g. store object[string: whatever] at data.p.q.r, not data.p.q.r[x] or data.p.q.r[x].y[z]
objPath := path.DynamicSuffix()
path = path.GroundPrefix()
typeK := cpy.Get(last)
if typeK != nil && typeV != nil {
exist := env.tree.Get(path)
typeV = types.Or(types.Values(exist), typeV)
typeK = types.Or(types.Keys(exist), typeK)
tpe = types.NewObject(nil, types.NewDynamicProperty(typeK, typeV))
var err error
tpe, err = nestedObject(cpy, objPath, typeV)
if err != nil {
tc.err([]*Error{NewError(TypeErr, rule.Head.Location, err.Error())})
tpe = nil
}
} else {
if typeV != nil {
exist := env.tree.Get(path)
tpe = types.Or(typeV, exist)
tpe = typeV
}
}
case MultiValue:
typeK := cpy.Get(rule.Head.Key)
if typeK != nil {
exist := env.tree.Get(path)
typeK = types.Or(types.Keys(exist), typeK)
tpe = types.NewSet(typeK)
}
}
}
if tpe != nil {
env.tree.Put(path, tpe)
env.tree.Insert(path, tpe, env)
}
}
// nestedObject creates a nested structure of object types, where each term on path corresponds to a level in the
// nesting. Each term in the path only contributes to the dynamic portion of its corresponding object.
func nestedObject(env *TypeEnv, path Ref, tpe types.Type) (types.Type, error) {
if len(path) == 0 {
return tpe, nil
}
k := path[0]
typeV, err := nestedObject(env, path[1:], tpe)
if err != nil {
return nil, err
}
if typeV == nil {
return nil, nil
}
var dynamicProperty *types.DynamicProperty
typeK := env.Get(k)
if typeK == nil {
return nil, nil
}
dynamicProperty = types.NewDynamicProperty(typeK, typeV)
return types.NewObject(nil, dynamicProperty), nil
}
func (tc *typeChecker) checkExpr(env *TypeEnv, expr *Expr) *Error {
if err := tc.checkExprWith(env, expr, 0); err != nil {
return err
@@ -281,7 +311,18 @@ func (tc *typeChecker) checkExpr(env *TypeEnv, expr *Expr) *Error {
return nil
}
checker := tc.exprCheckers[expr.Operator().String()]
operator := expr.Operator().String()
// If the type checker wasn't provided with a required capabilities
// structure then just skip. In some cases, type checking might be run
// without the need to record what builtins are required.
if tc.required != nil {
if bi, ok := tc.builtins[operator]; ok {
tc.required.addBuiltinSorted(bi)
}
}
checker := tc.exprCheckers[operator]
if checker != nil {
return checker(env, expr)
}
@@ -389,7 +430,7 @@ func (tc *typeChecker) checkExprWith(env *TypeEnv, expr *Expr, i int) *Error {
switch v := valueType.(type) {
case *types.Function: // ...by function
if !unifies(targetType, valueType) {
return newArgError(expr.With[i].Loc(), target.Value.(Ref), "arity mismatch", v.Args(), t.NamedFuncArgs())
return newArgError(expr.With[i].Loc(), target.Value.(Ref), "arity mismatch", v.FuncArgs().Args, t.NamedFuncArgs())
}
default: // ... by value, nothing to check
}
@@ -1200,7 +1241,7 @@ func getRuleAnnotation(as *AnnotationSet, rule *Rule) (result []*SchemaAnnotatio
result = append(result, x.Schemas...)
}
if x := as.GetDocumentScope(rule.Path()); x != nil {
if x := as.GetDocumentScope(rule.Ref().GroundPrefix()); x != nil {
result = append(result, x.Schemas...)
}
+415 -199
View File
File diff suppressed because it is too large Load Diff
+187
View File
@@ -6,6 +6,7 @@ package ast
import (
"fmt"
"strings"
"github.com/open-policy-agent/opa/types"
"github.com/open-policy-agent/opa/util"
@@ -171,6 +172,11 @@ func (env *TypeEnv) getRefRec(node *typeTreeNode, ref, tail Ref) types.Type {
}
if node.Leaf() {
if node.children.Len() > 0 {
if child := node.Child(tail[0].Value); child != nil {
return env.getRefRec(child, ref, tail[1:])
}
}
return selectRef(node.Value(), tail)
}
@@ -304,6 +310,187 @@ func (n *typeTreeNode) Put(path Ref, tpe types.Type) {
curr.value = tpe
}
// Insert inserts tpe at path in the tree, but also merges the value into any types.Object present along that path.
// If a types.Object is inserted, any leafs already present further down the tree are merged into the inserted object.
// path must be ground.
func (n *typeTreeNode) Insert(path Ref, tpe types.Type, env *TypeEnv) {
curr := n
for i, term := range path {
c, ok := curr.children.Get(term.Value)
var child *typeTreeNode
if !ok {
child = newTypeTree()
child.key = term.Value
curr.children.Put(child.key, child)
} else {
child = c.(*typeTreeNode)
if child.value != nil && i+1 < len(path) {
// If child has an object value, merge the new value into it.
if o, ok := child.value.(*types.Object); ok {
var err error
child.value, err = insertIntoObject(o, path[i+1:], tpe, env)
if err != nil {
panic(fmt.Errorf("unreachable, insertIntoObject: %w", err))
}
}
}
}
curr = child
}
curr.value = mergeTypes(curr.value, tpe)
if _, ok := tpe.(*types.Object); ok && curr.children.Len() > 0 {
// merge all leafs into the inserted object
leafs := curr.Leafs()
for p, t := range leafs {
var err error
curr.value, err = insertIntoObject(curr.value.(*types.Object), *p, t, env)
if err != nil {
panic(fmt.Errorf("unreachable, insertIntoObject: %w", err))
}
}
}
}
// mergeTypes merges the types of 'a' and 'b'. If both are sets, their 'of' types are joined with an types.Or.
// If both are objects, the key types of their dynamic properties are joined with types.Or:s, and their value types
// are recursively merged (using mergeTypes).
// If 'a' and 'b' are both objects, and at least one of them have static properties, they are joined
// with an types.Or, instead of being merged.
// If 'a' is an Any containing an Object, and 'b' is an Object (or vice versa); AND both objects have no
// static properties, they are merged.
// If 'a' and 'b' are different types, they are joined with an types.Or.
func mergeTypes(a, b types.Type) types.Type {
if a == nil {
return b
}
if b == nil {
return a
}
switch a := a.(type) {
case *types.Object:
if bObj, ok := b.(*types.Object); ok && len(a.StaticProperties()) == 0 && len(bObj.StaticProperties()) == 0 {
if len(a.StaticProperties()) > 0 || len(bObj.StaticProperties()) > 0 {
return types.Or(a, bObj)
}
aDynProps := a.DynamicProperties()
bDynProps := bObj.DynamicProperties()
dynProps := types.NewDynamicProperty(
types.Or(aDynProps.Key, bDynProps.Key),
mergeTypes(aDynProps.Value, bDynProps.Value))
return types.NewObject(nil, dynProps)
} else if bAny, ok := b.(types.Any); ok && len(a.StaticProperties()) == 0 {
// If a is an object type with no static components ...
for _, t := range bAny {
if tObj, ok := t.(*types.Object); ok && len(tObj.StaticProperties()) == 0 {
// ... and b is a types.Any containing an object with no static components, we merge them.
aDynProps := a.DynamicProperties()
tDynProps := tObj.DynamicProperties()
tDynProps.Key = types.Or(tDynProps.Key, aDynProps.Key)
tDynProps.Value = types.Or(tDynProps.Value, aDynProps.Value)
return bAny
}
}
}
case *types.Set:
if bSet, ok := b.(*types.Set); ok {
return types.NewSet(types.Or(a.Of(), bSet.Of()))
}
case types.Any:
if _, ok := b.(types.Any); !ok {
return mergeTypes(b, a)
}
}
return types.Or(a, b)
}
func (n *typeTreeNode) String() string {
b := strings.Builder{}
if k := n.key; k != nil {
b.WriteString(k.String())
} else {
b.WriteString("-")
}
if v := n.value; v != nil {
b.WriteString(": ")
b.WriteString(v.String())
}
n.children.Iter(func(_, v util.T) bool {
if child, ok := v.(*typeTreeNode); ok {
b.WriteString("\n\t+ ")
s := child.String()
s = strings.ReplaceAll(s, "\n", "\n\t")
b.WriteString(s)
}
return false
})
return b.String()
}
func insertIntoObject(o *types.Object, path Ref, tpe types.Type, env *TypeEnv) (*types.Object, error) {
if len(path) == 0 {
return o, nil
}
key := env.Get(path[0].Value)
if len(path) == 1 {
var dynamicProps *types.DynamicProperty
if dp := o.DynamicProperties(); dp != nil {
dynamicProps = types.NewDynamicProperty(types.Or(o.DynamicProperties().Key, key), types.Or(o.DynamicProperties().Value, tpe))
} else {
dynamicProps = types.NewDynamicProperty(key, tpe)
}
return types.NewObject(o.StaticProperties(), dynamicProps), nil
}
child, err := insertIntoObject(types.NewObject(nil, nil), path[1:], tpe, env)
if err != nil {
return nil, err
}
var dynamicProps *types.DynamicProperty
if dp := o.DynamicProperties(); dp != nil {
dynamicProps = types.NewDynamicProperty(types.Or(o.DynamicProperties().Key, key), types.Or(o.DynamicProperties().Value, child))
} else {
dynamicProps = types.NewDynamicProperty(key, child)
}
return types.NewObject(o.StaticProperties(), dynamicProps), nil
}
func (n *typeTreeNode) Leafs() map[*Ref]types.Type {
leafs := map[*Ref]types.Type{}
n.children.Iter(func(k, v util.T) bool {
collectLeafs(v.(*typeTreeNode), nil, leafs)
return false
})
return leafs
}
func collectLeafs(n *typeTreeNode, path Ref, leafs map[*Ref]types.Type) {
nPath := append(path, NewTerm(n.key))
if n.Leaf() {
leafs[&nPath] = n.Value()
return
}
n.children.Iter(func(k, v util.T) bool {
collectLeafs(v.(*typeTreeNode), nPath, leafs)
return false
})
}
func (n *typeTreeNode) Value() types.Type {
return n.value
}
+11 -8
View File
@@ -173,6 +173,7 @@ func (i *baseDocEqIndex) AllRules(resolver ValueResolver) (*IndexResult, error)
result := NewIndexResult(i.kind)
result.Default = i.defaultRule
result.OnlyGroundRefs = i.onlyGroundRefs
result.Rules = make([]*Rule, 0, len(tr.ordering))
for _, pos := range tr.ordering {
@@ -482,8 +483,10 @@ func (node *trieNode) String() string {
if len(node.mappers) > 0 {
flags = append(flags, fmt.Sprintf("%d mapper(s)", len(node.mappers)))
}
if l := node.values.Len(); l > 0 {
flags = append(flags, fmt.Sprintf("%d value(s)", l))
if node.values != nil {
if l := node.values.Len(); l > 0 {
flags = append(flags, fmt.Sprintf("%d value(s)", l))
}
}
return strings.Join(flags, " ")
}
@@ -697,12 +700,6 @@ func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalRes
return node.Traverse(resolver, tr)
}
head := arr.Elem(0).Value
if !IsScalar(head) {
return nil
}
if node.any != nil {
err := node.any.traverseArray(resolver, tr, arr.Slice(1, -1))
if err != nil {
@@ -710,6 +707,12 @@ func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalRes
}
}
head := arr.Elem(0).Value
if !IsScalar(head) {
return nil
}
child, ok := node.scalars.Get(head)
if !ok {
return nil
+26 -8
View File
@@ -18,14 +18,15 @@ const bom = 0xFEFF
// Scanner is used to tokenize an input stream of
// Rego source code.
type Scanner struct {
offset int
row int
col int
bs []byte
curr rune
width int
errors []Error
keywords map[string]tokens.Token
offset int
row int
col int
bs []byte
curr rune
width int
errors []Error
keywords map[string]tokens.Token
regoV1Compatible bool
}
// Error represents a scanner error.
@@ -102,6 +103,23 @@ func (s *Scanner) AddKeyword(kw string, tok tokens.Token) {
}
}
func (s *Scanner) HasKeyword(keywords map[string]tokens.Token) bool {
for kw := range s.keywords {
if _, ok := keywords[kw]; ok {
return true
}
}
return false
}
func (s *Scanner) SetRegoV1Compatible() {
s.regoV1Compatible = true
}
func (s *Scanner) RegoV1Compatible() bool {
return s.regoV1Compatible
}
// WithKeywords returns a new copy of the Scanner struct `s`, with the set
// of known keywords being that of `s` with `kws` added.
func (s *Scanner) WithKeywords(kws map[string]tokens.Token) *Scanner {
+1 -1
View File
@@ -76,7 +76,7 @@ var strings = [...]string{
EOF: "eof",
Whitespace: "whitespace",
Comment: "comment",
Ident: "ident",
Ident: "identifier",
Package: "package",
Import: "import",
As: "as",
+36
View File
@@ -0,0 +1,36 @@
package json
// Options defines the options for JSON operations,
// currently only marshaling can be configured
type Options struct {
MarshalOptions MarshalOptions
}
// MarshalOptions defines the options for JSON marshaling,
// currently only toggling the marshaling of location information is supported
type MarshalOptions struct {
// IncludeLocation toggles the marshaling of location information
IncludeLocation NodeToggle
// IncludeLocationText additionally/optionally includes the text of the location
IncludeLocationText bool
// ExcludeLocationFile additionally/optionally excludes the file of the location
// Note that this is inverted (i.e. not "include" as the default needs to remain false)
ExcludeLocationFile bool
}
// NodeToggle is a generic struct to allow the toggling of
// settings for different ast node types
type NodeToggle struct {
Term bool
Package bool
Comment bool
Import bool
Rule bool
Head bool
Expr bool
SomeDecl bool
Every bool
With bool
Annotations bool
AnnotationsRef bool
}
+43
View File
@@ -3,8 +3,11 @@ package location
import (
"bytes"
"encoding/json"
"errors"
"fmt"
astJSON "github.com/open-policy-agent/opa/ast/json"
)
// Location records a position in source code
@@ -14,6 +17,9 @@ type Location struct {
Row int `json:"row"` // The line in the source.
Col int `json:"col"` // The column in the row.
Offset int `json:"-"` // The byte offset for the location in the source.
// JSONOptions specifies options for marshaling and unmarshalling of locations
JSONOptions astJSON.Options
}
// NewLocation returns a new Location object.
@@ -87,3 +93,40 @@ func (loc *Location) Compare(other *Location) int {
}
return 0
}
func (loc *Location) MarshalJSON() ([]byte, error) {
// structs are used here to preserve the field ordering of the original Location struct
if loc.JSONOptions.MarshalOptions.ExcludeLocationFile {
data := struct {
Row int `json:"row"`
Col int `json:"col"`
Text []byte `json:"text,omitempty"`
}{
Row: loc.Row,
Col: loc.Col,
}
if loc.JSONOptions.MarshalOptions.IncludeLocationText {
data.Text = loc.Text
}
return json.Marshal(data)
}
data := struct {
File string `json:"file"`
Row int `json:"row"`
Col int `json:"col"`
Text []byte `json:"text,omitempty"`
}{
Row: loc.Row,
Col: loc.Col,
File: loc.File,
}
if loc.JSONOptions.MarshalOptions.IncludeLocationText {
data.Text = loc.Text
}
return json.Marshal(data)
}
+5 -1
View File
@@ -1,7 +1,11 @@
package ast
import (
astJSON "github.com/open-policy-agent/opa/ast/json"
)
// customJSON is an interface that can be implemented by AST nodes that
// allows the parser to set options for JSON operations on that node.
type customJSON interface {
setJSONOptions(JSONOptions)
setJSONOptions(astJSON.Options)
}
+133 -72
View File
@@ -21,9 +21,12 @@ import (
"github.com/open-policy-agent/opa/ast/internal/scanner"
"github.com/open-policy-agent/opa/ast/internal/tokens"
astJSON "github.com/open-policy-agent/opa/ast/json"
"github.com/open-policy-agent/opa/ast/location"
)
var RegoV1CompatibleRef = Ref{VarTerm("rego"), StringTerm("v1")}
// Note: This state is kept isolated from the parser so that we
// can do efficient shallow copies of these values when doing a
// save() and restore().
@@ -84,7 +87,7 @@ func (c parsedTermCache) String() string {
s.WriteRune('{')
var e *parsedTermCacheItem
for e = c.m; e != nil; e = e.next {
fmt.Fprintf(&s, "%v", e)
s.WriteString(fmt.Sprintf("%v", e))
}
s.WriteRune('}')
return s.String()
@@ -101,37 +104,9 @@ type ParserOptions struct {
AllFutureKeywords bool
FutureKeywords []string
SkipRules bool
JSONOptions *JSONOptions
JSONOptions *astJSON.Options
unreleasedKeywords bool // TODO(sr): cleanup
}
// JSONOptions defines the options for JSON operations,
// currently only marshaling can be configured
type JSONOptions struct {
MarshalOptions JSONMarshalOptions
}
// JSONMarshalOptions defines the options for JSON marshaling,
// currently only toggling the marshaling of location information is supported
type JSONMarshalOptions struct {
IncludeLocation NodeToggle
}
// NodeToggle is a generic struct to allow the toggling of
// settings for different ast node types
type NodeToggle struct {
Term bool
Package bool
Comment bool
Import bool
Rule bool
Head bool
Expr bool
SomeDecl bool
Every bool
With bool
Annotations bool
AnnotationsRef bool
RegoV1Compatible bool
}
// NewParser creates and initializes a Parser.
@@ -207,9 +182,9 @@ func (p *Parser) WithSkipRules(skip bool) *Parser {
return p
}
// WithJSONOptions sets the JSONOptions which will be set on nodes to configure
// WithJSONOptions sets the Options which will be set on nodes to configure
// their JSON marshaling behavior.
func (p *Parser) WithJSONOptions(jsonOptions *JSONOptions) *Parser {
func (p *Parser) WithJSONOptions(jsonOptions *astJSON.Options) *Parser {
p.po.JSONOptions = jsonOptions
return p
}
@@ -356,9 +331,14 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {
s = p.save()
if imp := p.parseImport(); imp != nil {
if RegoRootDocument.Equal(imp.Path.Value.(Ref)[0]) {
p.regoV1Import(imp)
}
if FutureRootDocument.Equal(imp.Path.Value.(Ref)[0]) {
p.futureImport(imp, allowedFutureKeywords)
}
stmts = append(stmts, imp)
continue
} else if len(p.s.errors) > 0 {
@@ -561,9 +541,9 @@ func (p *Parser) parseImport() *Import {
path := imp.Path.Value.(Ref)
if !RootDocumentNames.Contains(path[0]) && !FutureRootDocument.Equal(path[0]) {
if !RootDocumentNames.Contains(path[0]) && !FutureRootDocument.Equal(path[0]) && !RegoRootDocument.Equal(path[0]) {
p.errorf(imp.Path.Location, "unexpected import path, must begin with one of: %v, got: %v",
RootDocumentNames.Union(NewSet(FutureRootDocument)),
RootDocumentNames.Union(NewSet(FutureRootDocument, RegoRootDocument)),
path[0])
return nil
}
@@ -609,26 +589,32 @@ func (p *Parser) parseRules() []*Rule {
return nil
}
if usesContains {
rule.Head.keywords = append(rule.Head.keywords, tokens.Contains)
}
if rule.Default {
if !p.validateDefaultRuleValue(&rule) {
return nil
}
if len(rule.Head.Args) > 0 {
if !p.validateDefaultRuleArgs(&rule) {
return nil
}
}
rule.Body = NewBody(NewExpr(BooleanTerm(true).SetLocation(rule.Location)).SetLocation(rule.Location))
return []*Rule{&rule}
}
if usesContains && !rule.Head.Reference.IsGround() {
p.error(p.s.Loc(), "multi-value rules need ground refs")
return nil
}
// back-compat with `p[x] { ... }``
hasIf := p.s.tok == tokens.If
// p[x] if ... becomes a single-value rule p[x]
if hasIf && !usesContains && len(rule.Head.Ref()) == 2 {
if rule.Head.Value == nil {
rule.Head.generatedValue = true
rule.Head.Value = BooleanTerm(true).SetLocation(rule.Head.Location)
} else {
// p[x] = y if becomes a single-value rule p[x] with value y, but needs name for compat
@@ -657,6 +643,7 @@ func (p *Parser) parseRules() []*Rule {
switch {
case hasIf:
rule.Head.keywords = append(rule.Head.keywords, tokens.If)
p.scan()
s := p.save()
if expr := p.parseLiteral(); expr != nil {
@@ -688,6 +675,7 @@ func (p *Parser) parseRules() []*Rule {
case usesContains:
rule.Body = NewBody(NewExpr(BooleanTerm(true).SetLocation(rule.Location)).SetLocation(rule.Location))
rule.generatedBody = true
return []*Rule{&rule}
default:
@@ -695,7 +683,7 @@ func (p *Parser) parseRules() []*Rule {
}
if p.s.tok == tokens.Else {
if r := rule.Head.Ref(); len(r) > 1 && !r[len(r)-1].Value.IsGround() {
if r := rule.Head.Ref(); len(r) > 1 && !r.IsGround() {
p.error(p.s.Loc(), "else keyword cannot be used on rules with variables in head")
return nil
}
@@ -737,6 +725,7 @@ func (p *Parser) parseRules() []*Rule {
// rule's head AST but have their location
// set to the rule body.
next.Head = rule.Head.Copy()
next.Head.keywords = rule.Head.keywords
for i := range next.Head.Args {
if v, ok := next.Head.Args[i].Value.(Var); ok && v.IsWildcard() {
next.Head.Args[i].Value = Var(p.genwildcard())
@@ -756,6 +745,7 @@ func (p *Parser) parseElse(head *Head) *Rule {
rule.SetLoc(p.s.Loc())
rule.Head = head.Copy()
rule.Head.generatedValue = false
for i := range rule.Head.Args {
if v, ok := rule.Head.Args[i].Value.(Var); ok && v.IsWildcard() {
rule.Head.Args[i].Value = Var(p.genwildcard())
@@ -771,6 +761,7 @@ func (p *Parser) parseElse(head *Head) *Rule {
switch p.s.tok {
case tokens.LBrace, tokens.If: // no value, but a body follows directly
rule.Head.generatedValue = true
rule.Head.Value = BooleanTerm(true)
case tokens.Assign, tokens.Unify:
rule.Head.Assign = tokens.Assign == p.s.tok
@@ -786,42 +777,37 @@ func (p *Parser) parseElse(head *Head) *Rule {
}
hasIf := p.s.tok == tokens.If
hasLBrace := p.s.tok == tokens.LBrace
if hasIf {
p.scan()
s := p.save()
if expr := p.parseLiteral(); expr != nil {
// NOTE(sr): set literals are never false or undefined, so parsing this as
// p if false else if { true }
// ^^^^^^^^ set of one element, `true`
// isn't valid.
isSetLiteral := false
if t, ok := expr.Terms.(*Term); ok {
_, isSetLiteral = t.Value.(Set)
}
// expr.Term is []*Term or Every
if !isSetLiteral {
rule.Body.Append(expr)
setLocRecursive(rule.Body, rule.Location)
return &rule
}
}
p.restore(s)
}
if p.s.tok != tokens.LBrace {
if !hasIf && !hasLBrace {
rule.Body = NewBody(NewExpr(BooleanTerm(true)))
rule.generatedBody = true
setLocRecursive(rule.Body, rule.Location)
return &rule
}
p.scan()
if rule.Body = p.parseBody(tokens.RBrace); rule.Body == nil {
return nil
if hasIf {
rule.Head.keywords = append(rule.Head.keywords, tokens.If)
p.scan()
}
p.scan()
if p.s.tok == tokens.LBrace {
p.scan()
if rule.Body = p.parseBody(tokens.RBrace); rule.Body == nil {
return nil
}
p.scan()
} else if p.s.tok != tokens.EOF {
expr := p.parseLiteral()
if expr == nil {
return nil
}
rule.Body.Append(expr)
setLocRecursive(rule.Body, rule.Location)
} else {
p.illegal("rule body expected")
return nil
}
if p.s.tok == tokens.Else {
if rule.Else = p.parseElse(head); rule.Else == nil {
@@ -832,7 +818,6 @@ func (p *Parser) parseElse(head *Head) *Rule {
}
func (p *Parser) parseHead(defaultRule bool) (*Head, bool) {
head := &Head{}
loc := p.s.Loc()
defer func() {
@@ -855,7 +840,9 @@ func (p *Parser) parseHead(defaultRule bool) (*Head, bool) {
switch x := ref.Value.(type) {
case Var:
head = NewHead(x)
// Modify the code to add the location to the head ref
// and set the head ref's jsonOptions.
head = VarHead(x, ref.Location, p.po.JSONOptions)
case Ref:
head = RefHead(x)
case Call:
@@ -922,6 +909,7 @@ func (p *Parser) parseHead(defaultRule bool) (*Head, bool) {
if head.Value == nil && head.Key == nil {
if len(head.Ref()) != 2 || len(head.Args) > 0 {
head.generatedValue = true
head.Value = BooleanTerm(true).SetLocation(head.Location)
}
}
@@ -2001,7 +1989,7 @@ func (p *Parser) error(loc *location.Location, reason string) {
func (p *Parser) errorf(loc *location.Location, f string, a ...interface{}) {
msg := strings.Builder{}
fmt.Fprintf(&msg, f, a...)
msg.WriteString(fmt.Sprintf(f, a...))
switch len(p.s.hints) {
case 0: // nothing to do
@@ -2176,6 +2164,38 @@ func (p *Parser) validateDefaultRuleValue(rule *Rule) bool {
return valid
}
func (p *Parser) validateDefaultRuleArgs(rule *Rule) bool {
valid := true
vars := NewVarSet()
vis := NewGenericVisitor(func(x interface{}) bool {
switch x := x.(type) {
case Var:
if vars.Contains(x) {
p.error(rule.Loc(), fmt.Sprintf("illegal default rule (arguments cannot be repeated %v)", x))
valid = false
return true
}
vars.Add(x)
case *Term:
switch v := x.Value.(type) {
case Var: // do nothing
default:
p.error(rule.Loc(), fmt.Sprintf("illegal default rule (arguments cannot contain %v)", TypeName(v)))
valid = false
return true
}
}
return false
})
vis.Walk(rule.Head.Args)
return valid
}
// We explicitly use yaml unmarshalling, to accommodate for the '_' in 'related_resources',
// which isn't handled properly by json for some reason.
type rawAnnotation struct {
@@ -2508,6 +2528,11 @@ func (p *Parser) futureImport(imp *Import, allowedFutureKeywords map[string]toke
return
}
if p.s.s.RegoV1Compatible() {
p.errorf(imp.Path.Location, "the `%s` import implies `future.keywords`, these are therefore mutually exclusive", RegoV1CompatibleRef)
return
}
kwds := make([]string, 0, len(allowedFutureKeywords))
for k := range allowedFutureKeywords {
kwds = append(kwds, k)
@@ -2535,3 +2560,39 @@ func (p *Parser) futureImport(imp *Import, allowedFutureKeywords map[string]toke
p.s.s.AddKeyword(kw, allowedFutureKeywords[kw])
}
}
func (p *Parser) regoV1Import(imp *Import) {
if !p.po.Capabilities.ContainsFeature(FeatureRegoV1Import) {
p.errorf(imp.Path.Location, "invalid import, `%s` is not supported by current capabilities", 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, must be `%s`", RegoV1CompatibleRef)
return
}
if imp.Alias != "" {
p.errorf(imp.Path.Location, "`rego` imports cannot be aliased")
return
}
// import all future keywords with the rego.v1 import
kwds := make([]string, 0, len(futureKeywords))
for k := range futureKeywords {
kwds = append(kwds, k)
}
if p.s.s.HasKeyword(futureKeywords) && !p.s.s.RegoV1Compatible() {
// We have imported future keywords, but they didn't come from another `rego.v1` import.
p.errorf(imp.Path.Location, "the `%s` import implies `future.keywords`, these are therefore mutually exclusive", RegoV1CompatibleRef)
return
}
p.s.s.SetRegoV1Compatible()
for _, kw := range kwds {
p.s.s.AddKeyword(kw, futureKeywords[kw])
}
}
+112 -34
View File
@@ -16,6 +16,9 @@ import (
"fmt"
"strings"
"unicode"
"github.com/open-policy-agent/opa/ast/internal/tokens"
astJSON "github.com/open-policy-agent/opa/ast/json"
)
// MustParseBody returns a parsed body.
@@ -244,7 +247,9 @@ func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, erro
var head *Head
if v, ok := lhs.Value.(Var); ok {
head = NewHead(v)
// Modify the code to add the location to the head ref
// and set the head ref's jsonOptions.
head = VarHead(v, lhs.Location, &lhs.jsonOptions)
} else if r, ok := lhs.Value.(Ref); ok { // groundness ?
if _, ok := r[0].Value.(Var); !ok {
return nil, fmt.Errorf("invalid rule head: %v", r)
@@ -258,14 +263,17 @@ func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, erro
}
head.Value = rhs
head.Location = lhs.Location
head.setJSONOptions(lhs.jsonOptions)
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
setJSONOptions(body, &rhs.jsonOptions)
return &Rule{
Location: lhs.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location),
),
Module: module,
Location: lhs.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: lhs.jsonOptions,
}, nil
}
@@ -279,15 +287,20 @@ func ParseCompleteDocRuleWithDotsFromTerm(module *Module, term *Term) (*Rule, er
return nil, fmt.Errorf("invalid rule head: %v", ref)
}
head := RefHead(ref, BooleanTerm(true).SetLocation(term.Location))
head.generatedValue = true
head.Location = term.Location
head.jsonOptions = term.jsonOptions
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location))
setJSONOptions(body, &term.jsonOptions)
return &Rule{
Location: term.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location),
),
Module: module,
Body: body,
Module: module,
jsonOptions: term.jsonOptions,
}, nil
}
@@ -309,14 +322,17 @@ func ParsePartialObjectDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule,
head.Key = ref[1]
}
head.Location = rhs.Location
head.jsonOptions = rhs.jsonOptions
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
setJSONOptions(body, &rhs.jsonOptions)
rule := &Rule{
Location: rhs.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location),
),
Module: module,
Location: rhs.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: rhs.jsonOptions,
}
return rule, nil
@@ -340,18 +356,23 @@ func ParsePartialSetDocRuleFromTerm(module *Module, term *Term) (*Rule, error) {
if !ok {
return nil, fmt.Errorf("%vs cannot be used for rule head", TypeName(term.Value))
}
head = NewHead(v)
// Modify the code to add the location to the head ref
// and set the head ref's jsonOptions.
head = VarHead(v, ref[0].Location, &ref[0].jsonOptions)
head.Key = ref[1]
}
head.Location = term.Location
head.jsonOptions = term.jsonOptions
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location))
setJSONOptions(body, &term.jsonOptions)
rule := &Rule{
Location: term.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location),
),
Module: module,
Location: term.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: term.jsonOptions,
}
return rule, nil
@@ -377,12 +398,17 @@ func ParseRuleFromCallEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) {
head := RefHead(ref, rhs)
head.Location = lhs.Location
head.Args = Args(call[1:])
head.jsonOptions = lhs.jsonOptions
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
setJSONOptions(body, &rhs.jsonOptions)
rule := &Rule{
Location: lhs.Location,
Head: head,
Body: NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location)),
Module: module,
Location: lhs.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: lhs.jsonOptions,
}
return rule, nil
@@ -404,12 +430,17 @@ func ParseRuleFromCallExpr(module *Module, terms []*Term) (*Rule, error) {
head := RefHead(ref, BooleanTerm(true).SetLocation(loc))
head.Location = loc
head.Args = terms[1:]
head.jsonOptions = terms[0].jsonOptions
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(loc)).SetLocation(loc))
setJSONOptions(body, &terms[0].jsonOptions)
rule := &Rule{
Location: loc,
Head: head,
Module: module,
Body: NewBody(NewExpr(BooleanTerm(true).SetLocation(loc)).SetLocation(loc)),
Location: loc,
Head: head,
Module: module,
Body: body,
jsonOptions: terms[0].jsonOptions,
}
return rule, nil
}
@@ -446,7 +477,7 @@ func ParseModuleWithOpts(filename, input string, popts ParserOptions) (*Module,
if err != nil {
return nil, err
}
return parseModule(filename, stmts, comments)
return parseModule(filename, stmts, comments, popts.RegoV1Compatible)
}
// ParseBody returns exactly one body.
@@ -606,7 +637,7 @@ func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Sta
return stmts, comments, nil
}
func parseModule(filename string, stmts []Statement, comments []*Comment) (*Module, error) {
func parseModule(filename string, stmts []Statement, comments []*Comment, regoV1Compatible bool) (*Module, error) {
if len(stmts) == 0 {
return nil, NewError(ParseErr, &Location{File: filename}, "empty module")
@@ -627,11 +658,15 @@ func parseModule(filename string, stmts []Statement, comments []*Comment) (*Modu
// The comments slice only holds comments that were not their own statements.
mod.Comments = append(mod.Comments, comments...)
mod.regoV1Compatible = regoV1Compatible
for i, stmt := range stmts[1:] {
switch stmt := stmt.(type) {
case *Import:
mod.Imports = append(mod.Imports, stmt)
if Compare(stmt.Path.Value, RegoV1CompatibleRef) == 0 {
mod.regoV1Compatible = true
}
case *Rule:
setRuleModule(stmt, mod)
mod.Rules = append(mod.Rules, stmt)
@@ -641,6 +676,7 @@ func parseModule(filename string, stmts []Statement, comments []*Comment) (*Modu
errs = append(errs, NewError(ParseErr, stmt[0].Location, err.Error()))
continue
}
rule.generatedBody = true
mod.Rules = append(mod.Rules, rule)
// NOTE(tsandall): the statement should now be interpreted as a
@@ -658,6 +694,29 @@ func parseModule(filename string, stmts []Statement, comments []*Comment) (*Modu
}
}
if mod.regoV1Compatible {
for _, rule := range mod.Rules {
for r := rule; r != nil; r = r.Else {
var t string
if r.isFunction() {
t = "function"
} else {
t = "rule"
}
if r.generatedBody && r.Head.generatedValue {
errs = append(errs, NewError(ParseErr, r.Location, "%s must have value assignment and/or body declaration", t))
}
if r.Body != nil && !r.generatedBody && !ruleDeclarationHasKeyword(r, tokens.If) && !r.Default {
errs = append(errs, NewError(ParseErr, r.Location, "`if` keyword is required before %s body", t))
}
if r.Head.RuleKind() == MultiValue && !ruleDeclarationHasKeyword(r, tokens.Contains) {
errs = append(errs, NewError(ParseErr, r.Location, "`contains` keyword is required for partial set rules"))
}
}
}
}
if len(errs) > 0 {
return nil, errs
}
@@ -671,6 +730,15 @@ func parseModule(filename string, stmts []Statement, comments []*Comment) (*Modu
return mod, nil
}
func ruleDeclarationHasKeyword(rule *Rule, keyword tokens.Token) bool {
for _, kw := range rule.Head.keywords {
if kw == keyword {
return true
}
}
return false
}
func newScopeAttachmentErr(a *Annotations, want string) *Error {
var have string
if a.node != nil {
@@ -686,6 +754,16 @@ func setRuleModule(rule *Rule, module *Module) {
}
}
func setJSONOptions(x interface{}, jsonOptions *astJSON.Options) {
vis := NewGenericVisitor(func(x interface{}) bool {
if x, ok := x.(customJSON); ok {
x.setJSONOptions(*jsonOptions)
}
return false
})
vis.Walk(x)
}
// ParserErrorDetail holds additional details for parser errors.
type ParserErrorDetail struct {
Line string `json:"line"`
+83 -24
View File
@@ -12,6 +12,8 @@ import (
"strings"
"time"
"github.com/open-policy-agent/opa/ast/internal/tokens"
astJSON "github.com/open-policy-agent/opa/ast/json"
"github.com/open-policy-agent/opa/util"
)
@@ -42,6 +44,10 @@ var FunctionArgRootDocument = VarTerm("args")
// features.
var FutureRootDocument = VarTerm("future")
// RegoRootDocument names the document containing new, to-become-default,
// features in a future versioned release.
var RegoRootDocument = VarTerm("rego")
// RootDocumentNames contains the names of top-level documents that can be
// referred to in modules and queries.
//
@@ -139,12 +145,13 @@ type (
// within a namespace (defined by the package) and optional
// dependencies on external documents (defined by imports).
Module struct {
Package *Package `json:"package"`
Imports []*Import `json:"imports,omitempty"`
Annotations []*Annotations `json:"annotations,omitempty"`
Rules []*Rule `json:"rules,omitempty"`
Comments []*Comment `json:"comments,omitempty"`
stmts []Statement
Package *Package `json:"package"`
Imports []*Import `json:"imports,omitempty"`
Annotations []*Annotations `json:"annotations,omitempty"`
Rules []*Rule `json:"rules,omitempty"`
Comments []*Comment `json:"comments,omitempty"`
stmts []Statement
regoV1Compatible bool
}
// Comment contains the raw text from the comment in the definition.
@@ -153,7 +160,7 @@ type (
Text []byte
Location *Location
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// Package represents the namespace of the documents produced
@@ -162,7 +169,7 @@ type (
Path Ref `json:"path"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// Import represents a dependency on a document outside of the policy
@@ -172,7 +179,7 @@ type (
Alias Var `json:"alias,omitempty"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// Rule represents a rule as defined in the language. Rules define the
@@ -190,7 +197,8 @@ type (
// on the rule (e.g., printing, comparison, visiting, etc.)
Module *Module `json:"-"`
jsonOptions JSONOptions
generatedBody bool
jsonOptions astJSON.Options
}
// Head represents the head of a rule.
@@ -203,7 +211,9 @@ type (
Assign bool `json:"assign,omitempty"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
keywords []tokens.Token
generatedValue bool
jsonOptions astJSON.Options
}
// Args represents zero or more arguments to a rule.
@@ -222,7 +232,7 @@ type (
Negated bool `json:"negated,omitempty"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// SomeDecl represents a variable declaration statement. The symbols are variables.
@@ -230,7 +240,7 @@ type (
Symbols []*Term `json:"symbols"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
Every struct {
@@ -240,7 +250,7 @@ type (
Body Body `json:"body"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// With represents a modifier on an expression.
@@ -249,7 +259,7 @@ type (
Value *Term `json:"value"`
Location *Location `json:"location,omitempty"`
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
)
@@ -428,10 +438,13 @@ func (c *Comment) Equal(other *Comment) bool {
return c.Location.Equal(other.Location) && bytes.Equal(c.Text, other.Text)
}
func (c *Comment) setJSONOptions(opts JSONOptions) {
func (c *Comment) setJSONOptions(opts astJSON.Options) {
// Note: this is not used for location since Comments use default JSON marshaling
// behavior with struct field names in JSON.
c.jsonOptions = opts
if c.Location != nil {
c.Location.JSONOptions = opts
}
}
// Compare returns an integer indicating whether pkg is less than, equal to,
@@ -478,8 +491,11 @@ func (pkg *Package) String() string {
return fmt.Sprintf("package %v", path)
}
func (pkg *Package) setJSONOptions(opts JSONOptions) {
func (pkg *Package) setJSONOptions(opts astJSON.Options) {
pkg.jsonOptions = opts
if pkg.Location != nil {
pkg.Location.JSONOptions = opts
}
}
func (pkg *Package) MarshalJSON() ([]byte, error) {
@@ -588,8 +604,11 @@ func (imp *Import) String() string {
return strings.Join(buf, " ")
}
func (imp *Import) setJSONOptions(opts JSONOptions) {
func (imp *Import) setJSONOptions(opts astJSON.Options) {
imp.jsonOptions = opts
if imp.Location != nil {
imp.Location.JSONOptions = opts
}
}
func (imp *Import) MarshalJSON() ([]byte, error) {
@@ -699,8 +718,15 @@ func (rule *Rule) String() string {
return strings.Join(buf, " ")
}
func (rule *Rule) setJSONOptions(opts JSONOptions) {
func (rule *Rule) isFunction() bool {
return len(rule.Head.Args) > 0
}
func (rule *Rule) setJSONOptions(opts astJSON.Options) {
rule.jsonOptions = opts
if rule.Location != nil {
rule.Location.JSONOptions = opts
}
}
func (rule *Rule) MarshalJSON() ([]byte, error) {
@@ -769,6 +795,17 @@ func NewHead(name Var, args ...*Term) *Head {
return head
}
// VarHead creates a head object, initializes its Name, Location, and Options,
// and returns the new head.
func VarHead(name Var, location *Location, jsonOpts *astJSON.Options) *Head {
h := NewHead(name)
h.Reference[0].Location = location
if jsonOpts != nil {
h.Reference[0].setJSONOptions(*jsonOpts)
}
return h
}
// RefHead returns a new Head object with the passed Ref. If args are provided,
// the first will be used for the value.
func RefHead(ref Ref, args ...*Term) *Head {
@@ -881,6 +918,7 @@ func (head *Head) Copy() *Head {
cpy.Args = head.Args.Copy()
cpy.Key = head.Key.Copy()
cpy.Value = head.Value.Copy()
cpy.keywords = nil
return &cpy
}
@@ -915,8 +953,11 @@ func (head *Head) String() string {
return buf.String()
}
func (head *Head) setJSONOptions(opts JSONOptions) {
func (head *Head) setJSONOptions(opts astJSON.Options) {
head.jsonOptions = opts
if head.Location != nil {
head.Location.JSONOptions = opts
}
}
func (head *Head) MarshalJSON() ([]byte, error) {
@@ -975,6 +1016,12 @@ func (head *Head) SetLoc(loc *Location) {
head.Location = loc
}
func (head *Head) HasDynamicRef() bool {
pos := head.Reference.Dynamic()
// Ref is dynamic if it has one non-constant term that isn't the first or last term or if it's a partial set rule.
return pos > 0 && (pos < len(head.Reference)-1 || head.RuleKind() == MultiValue)
}
// Copy returns a deep copy of a.
func (a Args) Copy() Args {
cpy := Args{}
@@ -1459,8 +1506,11 @@ func (expr *Expr) String() string {
return strings.Join(buf, " ")
}
func (expr *Expr) setJSONOptions(opts JSONOptions) {
func (expr *Expr) setJSONOptions(opts astJSON.Options) {
expr.jsonOptions = opts
if expr.Location != nil {
expr.Location.JSONOptions = opts
}
}
func (expr *Expr) MarshalJSON() ([]byte, error) {
@@ -1555,8 +1605,11 @@ func (d *SomeDecl) Hash() int {
return termSliceHash(d.Symbols)
}
func (d *SomeDecl) setJSONOptions(opts JSONOptions) {
func (d *SomeDecl) setJSONOptions(opts astJSON.Options) {
d.jsonOptions = opts
if d.Location != nil {
d.Location.JSONOptions = opts
}
}
func (d *SomeDecl) MarshalJSON() ([]byte, error) {
@@ -1629,8 +1682,11 @@ func (q *Every) KeyValueVars() VarSet {
return vis.vars
}
func (q *Every) setJSONOptions(opts JSONOptions) {
func (q *Every) setJSONOptions(opts astJSON.Options) {
q.jsonOptions = opts
if q.Location != nil {
q.Location.JSONOptions = opts
}
}
func (q *Every) MarshalJSON() ([]byte, error) {
@@ -1708,8 +1764,11 @@ func (w *With) SetLoc(loc *Location) {
w.Location = loc
}
func (w *With) setJSONOptions(opts JSONOptions) {
func (w *With) setJSONOptions(opts astJSON.Options) {
w.jsonOptions = opts
if w.Location != nil {
w.Location.JSONOptions = opts
}
}
func (w *With) MarshalJSON() ([]byte, error) {
+126
View File
@@ -0,0 +1,126 @@
package ast
func checkDuplicateImports(modules []*Module) (errors Errors) {
for _, module := range modules {
processedImports := map[Var]*Import{}
for _, imp := range module.Imports {
name := imp.Name()
if processed, conflict := processedImports[name]; conflict {
errors = append(errors, NewError(CompileErr, imp.Location, "import must not shadow %v", processed))
} else {
processedImports[name] = imp
}
}
}
return
}
func checkRootDocumentOverrides(node interface{}) Errors {
errors := Errors{}
WalkRules(node, func(rule *Rule) bool {
var name string
if len(rule.Head.Reference) > 0 {
name = rule.Head.Reference[0].Value.(Var).String()
} else {
name = rule.Head.Name.String()
}
if RootDocumentRefs.Contains(RefTerm(VarTerm(name))) {
errors = append(errors, NewError(CompileErr, rule.Location, "rules must not shadow %v (use a different rule name)", name))
}
for _, arg := range rule.Head.Args {
if _, ok := arg.Value.(Ref); ok {
if RootDocumentRefs.Contains(arg) {
errors = append(errors, NewError(CompileErr, arg.Location, "args must not shadow %v (use a different variable name)", arg))
}
}
}
return true
})
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))
}
}
return false
})
return errors
}
func walkCalls(node interface{}, f func(interface{}) bool) {
vis := &GenericVisitor{func(x interface{}) bool {
switch x := x.(type) {
case Call:
return f(x)
case *Expr:
if x.IsCall() {
return f(x)
}
case *Head:
// GenericVisitor doesn't walk the rule head ref
walkCalls(x.Reference, f)
}
return false
}}
vis.Walk(node)
}
func checkDeprecatedBuiltins(deprecatedBuiltinsMap map[string]struct{}, node interface{}) Errors {
errs := make(Errors, 0)
walkCalls(node, func(x interface{}) bool {
var operator string
var loc *Location
switch x := x.(type) {
case *Expr:
operator = x.Operator().String()
loc = x.Loc()
case Call:
terms := []*Term(x)
if len(terms) > 0 {
operator = terms[0].Value.String()
loc = terms[0].Loc()
}
}
if operator != "" {
if _, ok := deprecatedBuiltinsMap[operator]; ok {
errs = append(errs, NewError(TypeErr, loc, "deprecated built-in function calls in expression: %v", operator))
}
}
return false
})
return errs
}
func checkDeprecatedBuiltinsForCurrentVersion(node interface{}) Errors {
deprecatedBuiltins := make(map[string]struct{})
capabilities := CapabilitiesForThisVersion()
for _, bi := range capabilities.Builtins {
if bi.IsDeprecated() {
deprecatedBuiltins[bi.Name] = struct{}{}
}
}
return checkDeprecatedBuiltins(deprecatedBuiltins, node)
}
// CheckRegoV1 checks the given module for errors that are specific to Rego v1
func CheckRegoV1(module *Module) Errors {
var errors Errors
errors = append(errors, checkDuplicateImports([]*Module{module})...)
errors = append(errors, checkRootDocumentOverrides(module)...)
errors = append(errors, checkDeprecatedBuiltinsForCurrentVersion(module)...)
return errors
}
+65 -9
View File
@@ -22,6 +22,7 @@ import (
"github.com/OneOfOne/xxhash"
astJSON "github.com/open-policy-agent/opa/ast/json"
"github.com/open-policy-agent/opa/ast/location"
"github.com/open-policy-agent/opa/util"
)
@@ -294,7 +295,7 @@ type Term struct {
Value Value `json:"value"` // the value of the Term as represented in Go
Location *Location `json:"location,omitempty"` // the location of the Term in the source
jsonOptions JSONOptions
jsonOptions astJSON.Options
}
// NewTerm returns a new Term object.
@@ -419,8 +420,11 @@ func (term *Term) IsGround() bool {
return term.Value.IsGround()
}
func (term *Term) setJSONOptions(opts JSONOptions) {
func (term *Term) setJSONOptions(opts astJSON.Options) {
term.jsonOptions = opts
if term.Location != nil {
term.Location.JSONOptions = opts
}
}
// MarshalJSON returns the JSON encoding of the term.
@@ -888,8 +892,8 @@ func PtrRef(head *Term, s string) (Ref, error) {
return Ref{head}, nil
}
parts := strings.Split(s, "/")
if max := math.MaxInt32; len(parts) >= max {
return nil, fmt.Errorf("path too long: %s, %d > %d (max)", s, len(parts), max)
if maxLen := math.MaxInt32; len(parts) >= maxLen {
return nil, fmt.Errorf("path too long: %s, %d > %d (max)", s, len(parts), maxLen)
}
ref := make(Ref, uint(len(parts))+1)
ref[0] = head
@@ -1028,6 +1032,20 @@ func (ref Ref) ConstantPrefix() Ref {
return ref[:i]
}
func (ref Ref) StringPrefix() Ref {
r := ref.Copy()
for i := 1; i < len(ref); i++ {
switch r[i].Value.(type) {
case String: // pass
default: // cut off
return r[:i]
}
}
return r
}
// GroundPrefix returns the ground portion of the ref starting from the head. By
// definition, the head of the reference is always ground.
func (ref Ref) GroundPrefix() Ref {
@@ -1043,6 +1061,14 @@ func (ref Ref) GroundPrefix() Ref {
return prefix
}
func (ref Ref) DynamicSuffix() Ref {
i := ref.Dynamic()
if i < 0 {
return nil
}
return ref[i:]
}
// IsGround returns true if all of the parts of the Ref are ground.
func (ref Ref) IsGround() bool {
if len(ref) == 0 {
@@ -1078,6 +1104,10 @@ func (ref Ref) Ptr() (string, error) {
var varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
func IsVarCompatibleString(s string) bool {
return varRegexp.MatchString(s)
}
func (ref Ref) String() string {
if len(ref) == 0 {
return ""
@@ -1840,22 +1870,34 @@ func ObjectTerm(o ...[2]*Term) *Term {
}
func LazyObject(blob map[string]interface{}) Object {
return &lazyObj{native: blob}
return &lazyObj{native: blob, cache: map[string]Value{}}
}
type lazyObj struct {
strict Object
cache map[string]Value
native map[string]interface{}
}
func (l *lazyObj) force() Object {
if l.strict == nil {
l.strict = MustInterfaceToValue(l.native).(Object)
// NOTE(jf): a possible performance improvement here would be to check how many
// entries have been realized to AST in the cache, and if some threshold compared to the
// total number of keys is exceeded, realize the remaining entries and set l.strict to l.cache.
l.cache = map[string]Value{} // We don't need the cache anymore; drop it to free up memory.
}
return l.strict
}
func (l *lazyObj) Compare(other Value) int {
o1 := sortOrder(l)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o2 < o1 {
return 1
}
return l.force().Compare(other)
}
@@ -1924,13 +1966,20 @@ func (l *lazyObj) Get(k *Term) *Term {
return l.strict.Get(k)
}
if s, ok := k.Value.(String); ok {
if v, ok := l.cache[string(s)]; ok {
return NewTerm(v)
}
if val, ok := l.native[string(s)]; ok {
var converted Value
switch val := val.(type) {
case map[string]interface{}:
return NewTerm(&lazyObj{native: val})
converted = LazyObject(val)
default:
return NewTerm(MustInterfaceToValue(val))
converted = MustInterfaceToValue(val)
}
l.cache[string(s)] = converted
return NewTerm(converted)
}
}
return nil
@@ -1985,13 +2034,20 @@ func (l *lazyObj) Find(path Ref) (Value, error) {
return l, nil
}
if p0, ok := path[0].Value.(String); ok {
if v, ok := l.cache[string(p0)]; ok {
return v.Find(path[1:])
}
if v, ok := l.native[string(p0)]; ok {
var converted Value
switch v := v.(type) {
case map[string]interface{}:
return (&lazyObj{native: v}).Find(path[1:])
converted = LazyObject(v)
default:
return MustInterfaceToValue(v).Find(path[1:])
converted = MustInterfaceToValue(v)
}
l.cache[string(p0)] = converted
return converted.Find(path[1:])
}
}
return nil, errFindNotFound
File diff suppressed because it is too large Load Diff