mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
Add new storage.Path type
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
// Copyright 2016 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
// Path refers to a document in storage.
|
||||
type Path []string
|
||||
|
||||
// ParsePath returns a new path for the given str.
|
||||
func ParsePath(str string) (path Path, ok bool) {
|
||||
if len(str) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if str[0] != '/' {
|
||||
return nil, false
|
||||
}
|
||||
if len(str) == 1 {
|
||||
return Path{}, true
|
||||
}
|
||||
parts := strings.Split(str[1:], "/")
|
||||
return parts, true
|
||||
}
|
||||
|
||||
// NewPathForRef returns a new path for the given ref.
|
||||
func NewPathForRef(ref ast.Ref) (path Path, err error) {
|
||||
|
||||
if len(ref) == 0 {
|
||||
return nil, fmt.Errorf("empty reference (indicates error in caller)")
|
||||
}
|
||||
|
||||
if len(ref) == 1 {
|
||||
return Path{}, nil
|
||||
}
|
||||
|
||||
for _, term := range ref[1:] {
|
||||
switch v := term.Value.(type) {
|
||||
case ast.String:
|
||||
path = append(path, string(v))
|
||||
case ast.Number:
|
||||
path = append(path, v.String())
|
||||
case ast.Boolean, ast.Null:
|
||||
return nil, notFoundRefError(ref, doesNotExistMsg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unresolved reference (indicates error in caller): %v", ref)
|
||||
}
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Compare performs lexigraphical comparison on p and other and returns -1 if p
|
||||
// is less than other, 0 if p is equal to other, or 1 if p is greater than
|
||||
// other.
|
||||
func (p Path) Compare(other Path) (cmp int) {
|
||||
min := len(p)
|
||||
if len(other) < min {
|
||||
min = len(other)
|
||||
}
|
||||
for i := 0; i < min; i++ {
|
||||
if cmp := strings.Compare(p[i], other[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
if len(p) < len(other) {
|
||||
return -1
|
||||
}
|
||||
if len(p) == len(other) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// Equal returns true if p is the same as other.
|
||||
func (p Path) Equal(other Path) bool {
|
||||
return p.Compare(other) == 0
|
||||
}
|
||||
|
||||
// HasPrefix returns true if p starts with other.
|
||||
func (p Path) HasPrefix(other Path) bool {
|
||||
if len(other) > len(p) {
|
||||
return false
|
||||
}
|
||||
for i := range other {
|
||||
if p[i] != other[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ref returns a ref that represents p rooted at head.
|
||||
func (p Path) Ref(head *ast.Term) (ref ast.Ref) {
|
||||
ref = make(ast.Ref, len(p)+1)
|
||||
ref[0] = head
|
||||
for i := range p {
|
||||
idx, err := strconv.ParseInt(p[i], 10, 64)
|
||||
if err == nil {
|
||||
ref[i+1] = ast.NumberTerm(float64(idx))
|
||||
} else {
|
||||
ref[i+1] = ast.StringTerm(p[i])
|
||||
}
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
func (p Path) String() string {
|
||||
return "/" + strings.Join(p, "/")
|
||||
}
|
||||
|
||||
// MustParsePath returns a new Path for s. If s cannot be parsed, this function
|
||||
// will panic. This is mostly for test purposes.
|
||||
func MustParsePath(s string) Path {
|
||||
path, ok := ParsePath(s)
|
||||
if !ok {
|
||||
panic(s)
|
||||
}
|
||||
return path
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2016 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
func TestNewPathForString(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
result Path
|
||||
ok bool
|
||||
}{
|
||||
{"", nil, false},
|
||||
{"foo", nil, false},
|
||||
{"/", Path{}, true},
|
||||
{"/", nil, true},
|
||||
{"/foo", Path{"foo"}, true},
|
||||
{"/foo/bar", Path{"foo", "bar"}, true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
result, ok := ParsePath(tc.input)
|
||||
if (tc.ok != ok) || !tc.result.Equal(result) {
|
||||
t.Errorf("For %v wanted (%v, %v) but got (%v, %v)", tc.input, tc.result, tc.ok, result, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPathForRef(t *testing.T) {
|
||||
tests := []struct {
|
||||
input ast.Ref
|
||||
result Path
|
||||
err error
|
||||
}{
|
||||
{ast.Ref{}, nil, fmt.Errorf("empty reference (indicates error in caller)")},
|
||||
{ast.MustParseRef("data.foo[x]"), nil, fmt.Errorf("unresolved reference (indicates error in caller): data.foo[x]")},
|
||||
{ast.MustParseRef("data.foo[true]"), nil, notFoundRefError(ast.MustParseRef("data.foo[true]"), doesNotExistMsg)},
|
||||
{ast.MustParseRef("data"), Path{}, nil},
|
||||
{ast.MustParseRef("data.foo"), Path{"foo"}, nil},
|
||||
{ast.MustParseRef("data.foo[1]"), Path{"foo", "1"}, nil},
|
||||
{ast.MustParseRef("data.foo.bar"), Path{"foo", "bar"}, nil},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
result, err := NewPathForRef(tc.input)
|
||||
if tc.err != nil && !reflect.DeepEqual(tc.err, err) {
|
||||
t.Errorf("For %v expected %v but got %v", tc.input, tc.err, err)
|
||||
} else if !result.Equal(tc.result) {
|
||||
t.Errorf("For %v expected %v but got %v", tc.input, tc.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathCompare(t *testing.T) {
|
||||
tests := []struct {
|
||||
a Path
|
||||
b Path
|
||||
result int
|
||||
}{
|
||||
{Path{}, Path{}, 0},
|
||||
{Path{}, Path{"x"}, -1},
|
||||
{Path{"x"}, Path{}, 1},
|
||||
{Path{"x"}, Path{"x"}, 0},
|
||||
{Path{"x"}, Path{"y"}, -1},
|
||||
{Path{"x"}, Path{"w"}, 1},
|
||||
{Path{"x"}, Path{"wz"}, 1},
|
||||
{Path{"x"}, Path{"xx"}, -1},
|
||||
{Path{"xx"}, Path{"x"}, 1},
|
||||
{Path{"xx"}, Path{"xx"}, 0},
|
||||
{Path{"xy"}, Path{"xx"}, 1},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
result := tc.a.Compare(tc.b)
|
||||
if result != tc.result {
|
||||
t.Errorf("For %v.Compare(%v) expected %v but got %v", tc.a, tc.b, tc.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
a Path
|
||||
b Path
|
||||
result bool
|
||||
}{
|
||||
{Path{}, Path{}, true},
|
||||
{Path{}, Path{"foo"}, false},
|
||||
{Path{"foo"}, Path{}, false},
|
||||
{Path{"foo", "bar"}, Path{"foo"}, false},
|
||||
{Path{"foo", "bar"}, Path{"foo", "bar"}, true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
result := tc.a.Equal(tc.b)
|
||||
if result != tc.result {
|
||||
t.Errorf("For %v.HasPrefix(%v) expected %v but got %v", tc.a, tc.b, tc.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathHasPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
a Path
|
||||
b Path
|
||||
result bool
|
||||
}{
|
||||
{Path{}, Path{}, true},
|
||||
{Path{}, Path{"foo"}, false},
|
||||
{Path{"foo"}, Path{}, true},
|
||||
{Path{"foo"}, Path{"bar"}, false},
|
||||
{Path{"bar"}, Path{"foo"}, false},
|
||||
{Path{"foo", "bar"}, Path{"foo"}, true},
|
||||
{Path{"foo", "bar"}, Path{"foo", "bar"}, true},
|
||||
{Path{"foo", "bar"}, Path{"foo", "bar", "baz"}, false},
|
||||
{Path{"foo", "bar", "baz"}, Path{}, true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
result := tc.a.HasPrefix(tc.b)
|
||||
if result != tc.result {
|
||||
t.Errorf("For %v.HasPrefix(%v) expected %v but got %v", tc.a, tc.b, tc.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathRef(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
head string
|
||||
ref string
|
||||
}{
|
||||
{"/", "data", "data"},
|
||||
{"/foo/bar", "data", "data.foo.bar"},
|
||||
{"/foo/bar/3", "data", "data.foo.bar[3]"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
path := MustParsePath(tc.path)
|
||||
head := ast.VarTerm(tc.head)
|
||||
ref := ast.MustParseRef(tc.ref)
|
||||
result := path.Ref(head)
|
||||
if !result.Equal(ref) {
|
||||
t.Errorf("Expected %v but got %v", ref, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user