snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
652
util/builtin_operators.go
Normal file
652
util/builtin_operators.go
Normal file
@@ -0,0 +1,652 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
|
||||
"github.com/casbin/casbin/v3/rbac"
|
||||
|
||||
"github.com/casbin/govaluate"
|
||||
)
|
||||
|
||||
var (
|
||||
keyMatch2Re = regexp.MustCompile(`:[^/]+`)
|
||||
keyMatch3Re = regexp.MustCompile(`\{[^/]+\}`)
|
||||
keyMatch4Re = regexp.MustCompile(`{([^/]+)}`)
|
||||
keyMatch5Re = regexp.MustCompile(`\{[^/]+\}`)
|
||||
keyGet2Re1 = regexp.MustCompile(`:[^/]+`)
|
||||
keyGet3Re1 = regexp.MustCompile(`\{[^/]+?\}`) // non-greedy match of `{...}` to support multiple {} in `/.../`
|
||||
reCache = map[string]*regexp.Regexp{}
|
||||
reCacheMu = sync.RWMutex{}
|
||||
)
|
||||
|
||||
// regexpMetaChars is exactly the set of characters that regexp.QuoteMeta escapes.
|
||||
// It is spelled out instead of calling QuoteMeta so that keyMatchShortcut never
|
||||
// allocates; TestRegexpMetaChars guards the two against drifting apart.
|
||||
const regexpMetaChars = `\.+*?()|[]{}^$`
|
||||
|
||||
// keyMatchShortcut answers KeyMatch2/3/4/5 for the two cases that need no regexp
|
||||
// work at all. ok reports whether matched is the final answer; when it is false
|
||||
// the caller falls through to its unchanged rewrite-and-match path.
|
||||
//
|
||||
// The two cases are:
|
||||
//
|
||||
// 1. key2 == "*", the bare wildcard used as the "all domains" key. The regexp
|
||||
// path reaches the same answer via "^*$", which only works by accident (see
|
||||
// issue #330); making it explicit is both faster and less fragile.
|
||||
//
|
||||
// 2. key2 is plain, i.e. it holds no regexp metacharacter and none of the
|
||||
// matcher's own pattern characters (extraChars, ":" for KeyMatch2's :param
|
||||
// dialect; the "{}" and "*" of the other matchers are already metacharacters).
|
||||
// Then "^"+key2+"$" can only match key2 itself, so key1 == key2 is the answer.
|
||||
//
|
||||
// Case 2 has to stay conservative because RegexMatch embeds key2 unescaped: a
|
||||
// stray metacharacter makes key2 behave as a regexp, so "acme.com" does match
|
||||
// "acmeXcom", and key1 == key2 is not sufficient either ("^a+b$" does not match
|
||||
// "a+b"). Any such key2 keeps taking the regexp path.
|
||||
//
|
||||
// This matters because a DomainMatchingFunc is called O(N*D) times while building
|
||||
// role links, and real deployments mostly hold concrete domains ("team/123") that
|
||||
// pay for the full pipeline only to come back false.
|
||||
func keyMatchShortcut(key1 string, key2 string, extraChars string) (matched bool, ok bool) {
|
||||
if key2 == "*" {
|
||||
return true, true
|
||||
}
|
||||
|
||||
if !strings.ContainsAny(key2, regexpMetaChars) && !strings.ContainsAny(key2, extraChars) {
|
||||
return key1 == key2, true
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
|
||||
// compiledPattern is the fully prepared form of a matcher's key2: the regexp that
|
||||
// key1 is tested against, plus the names of the pattern variables in the order
|
||||
// their capture groups appear (only KeyGet2/KeyGet3/KeyMatch4 use tokens).
|
||||
//
|
||||
// Everything in it is a pure function of key2, so it is built once per distinct
|
||||
// key2 and reused afterwards.
|
||||
type compiledPattern struct {
|
||||
re *regexp.Regexp
|
||||
tokens []string
|
||||
}
|
||||
|
||||
// patternCache memoizes compiledPattern by raw key2, one cache per matcher since
|
||||
// each of them reads key2 in its own dialect. Only compiling the regexp used to be
|
||||
// cached (see mustCompileOrGet), which still left every call rebuilding the regexp
|
||||
// source with a chain of ReplaceAll passes just to look it up. Keying on key2
|
||||
// instead makes the steady state a single map read.
|
||||
//
|
||||
// A policy holds a bounded set of patterns, so the cache converges to that set
|
||||
// after the first pass; sync.Map suits that read-mostly shape.
|
||||
type patternCache struct {
|
||||
m sync.Map // string -> *compiledPattern
|
||||
}
|
||||
|
||||
func (c *patternCache) get(key2 string, build func(key2 string) *compiledPattern) *compiledPattern {
|
||||
if v, ok := c.m.Load(key2); ok {
|
||||
return v.(*compiledPattern)
|
||||
}
|
||||
|
||||
p := build(key2)
|
||||
c.m.Store(key2, p)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
var (
|
||||
keyMatch2Cache patternCache
|
||||
keyMatch3Cache patternCache
|
||||
keyMatch4Cache patternCache
|
||||
keyMatch5Cache patternCache
|
||||
keyGet2Cache patternCache
|
||||
keyGet3Cache patternCache
|
||||
)
|
||||
|
||||
func mustCompileOrGet(key string) *regexp.Regexp {
|
||||
reCacheMu.RLock()
|
||||
re, ok := reCache[key]
|
||||
reCacheMu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
re = regexp.MustCompile(key)
|
||||
reCacheMu.Lock()
|
||||
reCache[key] = re
|
||||
reCacheMu.Unlock()
|
||||
}
|
||||
|
||||
return re
|
||||
}
|
||||
|
||||
// validate the variadic parameter size and type as string.
|
||||
func validateVariadicArgs(expectedLen int, args ...interface{}) error {
|
||||
if len(args) != expectedLen {
|
||||
return fmt.Errorf("expected %d arguments, but got %d", expectedLen, len(args))
|
||||
}
|
||||
|
||||
for _, p := range args {
|
||||
_, ok := p.(string)
|
||||
if !ok {
|
||||
return errors.New("argument must be a string")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate the variadic string parameter size.
|
||||
func validateVariadicStringArgs(expectedLen int, args ...string) error {
|
||||
if len(args) != expectedLen {
|
||||
return fmt.Errorf("expected %d arguments, but got %d", expectedLen, len(args))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyMatch determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
|
||||
// For example, "/foo/bar" matches "/foo/*".
|
||||
func KeyMatch(key1 string, key2 string) bool {
|
||||
i := strings.Index(key2, "*")
|
||||
if i == -1 {
|
||||
return key1 == key2
|
||||
}
|
||||
|
||||
if len(key1) > i {
|
||||
return key1[:i] == key2[:i]
|
||||
}
|
||||
return key1 == key2[:i]
|
||||
}
|
||||
|
||||
// KeyMatchFunc is the wrapper for KeyMatch.
|
||||
func KeyMatchFunc(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyMatch", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return KeyMatch(name1, name2), nil
|
||||
}
|
||||
|
||||
// KeyGet returns the matched part
|
||||
// For example, "/foo/bar/foo" matches "/foo/*"
|
||||
// "bar/foo" will been returned.
|
||||
func KeyGet(key1, key2 string) string {
|
||||
i := strings.Index(key2, "*")
|
||||
if i == -1 {
|
||||
return ""
|
||||
}
|
||||
if len(key1) > i {
|
||||
if key1[:i] == key2[:i] {
|
||||
return key1[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// KeyGetFunc is the wrapper for KeyGet.
|
||||
func KeyGetFunc(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyGet", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return KeyGet(name1, name2), nil
|
||||
}
|
||||
|
||||
// KeyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
|
||||
// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource".
|
||||
func KeyMatch2(key1 string, key2 string) bool {
|
||||
if matched, ok := keyMatchShortcut(key1, key2, ":"); ok {
|
||||
return matched
|
||||
}
|
||||
|
||||
return keyMatch2Cache.get(key2, buildKeyMatch2).re.MatchString(key1)
|
||||
}
|
||||
|
||||
func buildKeyMatch2(key2 string) *compiledPattern {
|
||||
key2 = strings.ReplaceAll(key2, "/*", "/.*")
|
||||
|
||||
key2 = keyMatch2Re.ReplaceAllString(key2, "$1[^/]+$2")
|
||||
|
||||
return &compiledPattern{re: mustCompileOrGet("^" + key2 + "$")}
|
||||
}
|
||||
|
||||
// KeyMatch2Func is the wrapper for KeyMatch2.
|
||||
func KeyMatch2Func(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyMatch2", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return KeyMatch2(name1, name2), nil
|
||||
}
|
||||
|
||||
// KeyGet2 returns value matched pattern
|
||||
// For example, "/resource1" matches "/:resource"
|
||||
// if the pathVar == "resource", then "resource1" will be returned.
|
||||
func KeyGet2(key1, key2 string, pathVar string) string {
|
||||
p := keyGet2Cache.get(key2, buildKeyGet2)
|
||||
|
||||
values := p.re.FindAllStringSubmatch(key1, -1)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
for i, key := range p.tokens {
|
||||
if pathVar == key {
|
||||
return values[0][i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildKeyGet2(key2 string) *compiledPattern {
|
||||
key2 = strings.ReplaceAll(key2, "/*", "/.*")
|
||||
|
||||
// The ":" is dropped here so that the lookup in KeyGet2 is a plain comparison.
|
||||
tokens := keyGet2Re1.FindAllString(key2, -1)
|
||||
for i, token := range tokens {
|
||||
tokens[i] = token[1:]
|
||||
}
|
||||
|
||||
key2 = keyGet2Re1.ReplaceAllString(key2, "$1([^/]+)$2")
|
||||
|
||||
return &compiledPattern{re: mustCompileOrGet("^" + key2 + "$"), tokens: tokens}
|
||||
}
|
||||
|
||||
// KeyGet2Func is the wrapper for KeyGet2.
|
||||
func KeyGet2Func(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(3, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyGet2", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
key := args[2].(string)
|
||||
|
||||
return KeyGet2(name1, name2, key), nil
|
||||
}
|
||||
|
||||
// KeyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
|
||||
// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}".
|
||||
func KeyMatch3(key1 string, key2 string) bool {
|
||||
if matched, ok := keyMatchShortcut(key1, key2, ""); ok {
|
||||
return matched
|
||||
}
|
||||
|
||||
return keyMatch3Cache.get(key2, buildKeyMatch3).re.MatchString(key1)
|
||||
}
|
||||
|
||||
func buildKeyMatch3(key2 string) *compiledPattern {
|
||||
key2 = strings.ReplaceAll(key2, "/*", "/.*")
|
||||
key2 = keyMatch3Re.ReplaceAllString(key2, "$1[^/]+$2")
|
||||
|
||||
return &compiledPattern{re: mustCompileOrGet("^" + key2 + "$")}
|
||||
}
|
||||
|
||||
// KeyMatch3Func is the wrapper for KeyMatch3.
|
||||
func KeyMatch3Func(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyMatch3", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return KeyMatch3(name1, name2), nil
|
||||
}
|
||||
|
||||
// KeyGet3 returns value matched pattern
|
||||
// For example, "project/proj_project1_admin/" matches "project/proj_{project}_admin/"
|
||||
// if the pathVar == "project", then "project1" will be returned.
|
||||
func KeyGet3(key1, key2 string, pathVar string) string {
|
||||
p := keyGet3Cache.get(key2, buildKeyGet3)
|
||||
|
||||
values := p.re.FindAllStringSubmatch(key1, -1)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
for i, key := range p.tokens {
|
||||
if pathVar == key {
|
||||
return values[0][i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildKeyGet3(key2 string) *compiledPattern {
|
||||
key2 = strings.ReplaceAll(key2, "/*", "/.*")
|
||||
|
||||
// The surrounding "{}" is dropped here so that the lookup in KeyGet3 is a plain
|
||||
// comparison.
|
||||
tokens := keyGet3Re1.FindAllString(key2, -1)
|
||||
for i, token := range tokens {
|
||||
tokens[i] = token[1 : len(token)-1]
|
||||
}
|
||||
|
||||
key2 = keyGet3Re1.ReplaceAllString(key2, "$1([^/]+?)$2")
|
||||
|
||||
return &compiledPattern{re: mustCompileOrGet("^" + key2 + "$"), tokens: tokens}
|
||||
}
|
||||
|
||||
// KeyGet3Func is the wrapper for KeyGet3.
|
||||
func KeyGet3Func(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(3, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyGet3", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
key := args[2].(string)
|
||||
|
||||
return KeyGet3(name1, name2, key), nil
|
||||
}
|
||||
|
||||
// KeyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
|
||||
// Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns:
|
||||
// "/parent/123/child/123" matches "/parent/{id}/child/{id}"
|
||||
// "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
|
||||
// But KeyMatch3 will match both.
|
||||
func KeyMatch4(key1 string, key2 string) bool {
|
||||
if matched, ok := keyMatchShortcut(key1, key2, ""); ok {
|
||||
return matched
|
||||
}
|
||||
|
||||
p := keyMatch4Cache.get(key2, buildKeyMatch4)
|
||||
|
||||
matches := p.re.FindStringSubmatch(key1)
|
||||
if matches == nil {
|
||||
return false
|
||||
}
|
||||
matches = matches[1:]
|
||||
|
||||
if len(p.tokens) != len(matches) {
|
||||
panic(errors.New("KeyMatch4: number of tokens is not equal to number of values"))
|
||||
}
|
||||
|
||||
values := map[string]string{}
|
||||
|
||||
for key, token := range p.tokens {
|
||||
if _, ok := values[token]; !ok {
|
||||
values[token] = matches[key]
|
||||
}
|
||||
if values[token] != matches[key] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func buildKeyMatch4(key2 string) *compiledPattern {
|
||||
key2 = strings.ReplaceAll(key2, "/*", "/.*")
|
||||
|
||||
tokens := []string{}
|
||||
|
||||
key2 = keyMatch4Re.ReplaceAllStringFunc(key2, func(s string) string {
|
||||
tokens = append(tokens, s[1:len(s)-1])
|
||||
return "([^/]+)"
|
||||
})
|
||||
|
||||
return &compiledPattern{re: mustCompileOrGet("^" + key2 + "$"), tokens: tokens}
|
||||
}
|
||||
|
||||
// KeyMatch4Func is the wrapper for KeyMatch4.
|
||||
func KeyMatch4Func(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyMatch4", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return KeyMatch4(name1, name2), nil
|
||||
}
|
||||
|
||||
// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *
|
||||
// For example,
|
||||
// - "/foo/bar?status=1&type=2" matches "/foo/bar"
|
||||
// - "/parent/child1" and "/parent/child1" matches "/parent/*"
|
||||
// - "/parent/child1?status=1" matches "/parent/*".
|
||||
func KeyMatch5(key1 string, key2 string) bool {
|
||||
i := strings.Index(key1, "?")
|
||||
|
||||
if i != -1 {
|
||||
key1 = key1[:i]
|
||||
}
|
||||
|
||||
// After the query string is stripped, so that key1 is compared in the same
|
||||
// shape the regexp path would have matched.
|
||||
if matched, ok := keyMatchShortcut(key1, key2, ""); ok {
|
||||
return matched
|
||||
}
|
||||
|
||||
return keyMatch5Cache.get(key2, buildKeyMatch5).re.MatchString(key1)
|
||||
}
|
||||
|
||||
func buildKeyMatch5(key2 string) *compiledPattern {
|
||||
key2 = strings.ReplaceAll(key2, "/*", "/.*")
|
||||
key2 = keyMatch5Re.ReplaceAllString(key2, "$1[^/]+$2")
|
||||
|
||||
return &compiledPattern{re: mustCompileOrGet("^" + key2 + "$")}
|
||||
}
|
||||
|
||||
// KeyMatch5Func is the wrapper for KeyMatch5.
|
||||
func KeyMatch5Func(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "keyMatch5", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return KeyMatch5(name1, name2), nil
|
||||
}
|
||||
|
||||
// RegexMatch determines whether key1 matches the pattern of key2 in regular expression.
|
||||
func RegexMatch(key1 string, key2 string) bool {
|
||||
return mustCompileOrGet(key2).MatchString(key1)
|
||||
}
|
||||
|
||||
// RegexMatchFunc is the wrapper for RegexMatch.
|
||||
func RegexMatchFunc(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "regexMatch", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return RegexMatch(name1, name2), nil
|
||||
}
|
||||
|
||||
// IPMatch determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern.
|
||||
// For example, "192.168.2.123" matches "192.168.2.0/24".
|
||||
func IPMatch(ip1 string, ip2 string) bool {
|
||||
objIP1 := net.ParseIP(ip1)
|
||||
if objIP1 == nil {
|
||||
panic("invalid argument: ip1 in IPMatch() function is not an IP address.")
|
||||
}
|
||||
|
||||
_, cidr, err := net.ParseCIDR(ip2)
|
||||
if err != nil {
|
||||
objIP2 := net.ParseIP(ip2)
|
||||
if objIP2 == nil {
|
||||
panic("invalid argument: ip2 in IPMatch() function is neither an IP address nor a CIDR.")
|
||||
}
|
||||
|
||||
return objIP1.Equal(objIP2)
|
||||
}
|
||||
|
||||
return cidr.Contains(objIP1)
|
||||
}
|
||||
|
||||
// IPMatchFunc is the wrapper for IPMatch.
|
||||
func IPMatchFunc(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "ipMatch", err)
|
||||
}
|
||||
|
||||
ip1 := args[0].(string)
|
||||
ip2 := args[1].(string)
|
||||
|
||||
return IPMatch(ip1, ip2), nil
|
||||
}
|
||||
|
||||
// GlobMatch determines whether key1 matches the pattern of key2 using glob pattern.
|
||||
func GlobMatch(key1 string, key2 string) (bool, error) {
|
||||
return doublestar.Match(key2, key1)
|
||||
}
|
||||
|
||||
// GlobMatchFunc is the wrapper for GlobMatch.
|
||||
func GlobMatchFunc(args ...interface{}) (interface{}, error) {
|
||||
if err := validateVariadicArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "globMatch", err)
|
||||
}
|
||||
|
||||
name1 := args[0].(string)
|
||||
name2 := args[1].(string)
|
||||
|
||||
return GlobMatch(name1, name2)
|
||||
}
|
||||
|
||||
// GenerateGFunction is the factory method of the g(_, _[, _]) function.
|
||||
// If useCache is true, results are memoized per unique argument combination for performance.
|
||||
// Set useCache to false when inputs are high-cardinality (e.g. UUIDs) to avoid unbounded memory growth.
|
||||
func GenerateGFunction(rm rbac.RoleManager, useCache bool) govaluate.ExpressionFunction {
|
||||
memorized := sync.Map{}
|
||||
return func(args ...interface{}) (interface{}, error) {
|
||||
// Like all our other govaluate functions, all args are strings.
|
||||
|
||||
if useCache {
|
||||
// Allocate and generate a cache key from the arguments...
|
||||
total := len(args)
|
||||
for _, a := range args {
|
||||
aStr := a.(string)
|
||||
total += len(aStr)
|
||||
}
|
||||
builder := strings.Builder{}
|
||||
builder.Grow(total)
|
||||
for _, arg := range args {
|
||||
builder.WriteByte(0)
|
||||
builder.WriteString(arg.(string))
|
||||
}
|
||||
key := builder.String()
|
||||
|
||||
// ...and see if we've already calculated this.
|
||||
if v, found := memorized.Load(key); found {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// If not, do the calculation.
|
||||
// There are guaranteed to be exactly 2 or 3 arguments.
|
||||
name1, name2 := args[0].(string), args[1].(string)
|
||||
var v interface{}
|
||||
if rm == nil {
|
||||
v = name1 == name2
|
||||
} else if len(args) == 2 {
|
||||
v, _ = rm.HasLink(name1, name2)
|
||||
} else {
|
||||
domain := args[2].(string)
|
||||
v, _ = rm.HasLink(name1, name2, domain)
|
||||
}
|
||||
|
||||
memorized.Store(key, v)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// No caching path.
|
||||
name1, name2 := args[0].(string), args[1].(string)
|
||||
var v interface{}
|
||||
if rm == nil {
|
||||
v = name1 == name2
|
||||
} else if len(args) == 2 {
|
||||
v, _ = rm.HasLink(name1, name2)
|
||||
} else {
|
||||
domain := args[2].(string)
|
||||
v, _ = rm.HasLink(name1, name2, domain)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateConditionalGFunction is the factory method of the g(_, _[, _]) function with conditions.
|
||||
func GenerateConditionalGFunction(crm rbac.ConditionalRoleManager) govaluate.ExpressionFunction {
|
||||
return func(args ...interface{}) (interface{}, error) {
|
||||
// Like all our other govaluate functions, all args are strings.
|
||||
var hasLink bool
|
||||
|
||||
name1, name2 := args[0].(string), args[1].(string)
|
||||
if crm == nil {
|
||||
hasLink = name1 == name2
|
||||
} else if len(args) == 2 {
|
||||
hasLink, _ = crm.HasLink(name1, name2)
|
||||
} else {
|
||||
domain := args[2].(string)
|
||||
hasLink, _ = crm.HasLink(name1, name2, domain)
|
||||
}
|
||||
|
||||
return hasLink, nil
|
||||
}
|
||||
}
|
||||
|
||||
// builtin LinkConditionFunc
|
||||
|
||||
// TimeMatchFunc is the wrapper for TimeMatch.
|
||||
func TimeMatchFunc(args ...string) (bool, error) {
|
||||
if err := validateVariadicStringArgs(2, args...); err != nil {
|
||||
return false, fmt.Errorf("%s: %w", "TimeMatch", err)
|
||||
}
|
||||
return TimeMatch(args[0], args[1])
|
||||
}
|
||||
|
||||
// TimeMatch determines whether the current time is between startTime and endTime.
|
||||
// You can use "_" to indicate that the parameter is ignored.
|
||||
func TimeMatch(startTime, endTime string) (bool, error) {
|
||||
now := time.Now()
|
||||
if startTime != "_" {
|
||||
if start, err := time.Parse("2006-01-02 15:04:05", startTime); err != nil {
|
||||
return false, err
|
||||
} else if !now.After(start) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if endTime != "_" {
|
||||
if end, err := time.Parse("2006-01-02 15:04:05", endTime); err != nil {
|
||||
return false, err
|
||||
} else if !now.Before(end) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
65
util/builtin_operators_b_test.go
Normal file
65
util/builtin_operators_b_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
// The key2 of each benchmark holds a pattern character, so the keyMatchShortcut
|
||||
// fast path does not apply and the regexp pipeline is what is being measured.
|
||||
|
||||
func BenchmarkKeyMatch2(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
KeyMatch2("/alice_data/resource1", "/:name/resource1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyMatch3(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
KeyMatch3("/proxy/myid/res/1", "/proxy/{id}/res/{rid}")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyMatch4(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
KeyMatch4("/parent/123/child/123", "/parent/{id}/child/{id}")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyMatch5(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
KeyMatch5("/parent/child1?status=1", "/parent/{id}")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyGet2(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
KeyGet2("/alice_data/resource1", "/:name/resource1", "name")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyGet3(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
KeyGet3("project/proj_project1_admin/", "project/proj_{project}_admin/", "project")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRegexMatch(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
RegexMatch("/topic/1", "^/topic/[0-9]+$")
|
||||
}
|
||||
}
|
||||
739
util/builtin_operators_test.go
Normal file
739
util/builtin_operators_test.go
Normal file
@@ -0,0 +1,739 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testKeyMatch(t *testing.T, key1 string, key2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes := KeyMatch(key1, key2)
|
||||
t.Logf("%s < %s: %t", key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyMatch(t *testing.T) {
|
||||
testKeyMatch(t, "/foo", "/foo", true)
|
||||
testKeyMatch(t, "/foo", "/foo*", true)
|
||||
testKeyMatch(t, "/foo", "/foo/*", false)
|
||||
testKeyMatch(t, "/foo/bar", "/foo", false)
|
||||
testKeyMatch(t, "/foo/bar", "/foo*", true)
|
||||
testKeyMatch(t, "/foo/bar", "/foo/*", true)
|
||||
testKeyMatch(t, "/foobar", "/foo", false)
|
||||
testKeyMatch(t, "/foobar", "/foo*", true)
|
||||
testKeyMatch(t, "/foobar", "/foo/*", false)
|
||||
}
|
||||
|
||||
func testKeyGet(t *testing.T, key1 string, key2 string, res string) {
|
||||
t.Helper()
|
||||
myRes := KeyGet(key1, key2)
|
||||
t.Logf(`%s < %s: "%s"`, key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf(`%s < %s: "%s", supposed to be "%s"`, key1, key2, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyGet(t *testing.T) {
|
||||
testKeyGet(t, "/foo", "/foo", "")
|
||||
testKeyGet(t, "/foo", "/foo*", "")
|
||||
testKeyGet(t, "/foo", "/foo/*", "")
|
||||
testKeyGet(t, "/foo/bar", "/foo", "")
|
||||
testKeyGet(t, "/foo/bar", "/foo*", "/bar")
|
||||
testKeyGet(t, "/foo/bar", "/foo/*", "bar")
|
||||
testKeyGet(t, "/foobar", "/foo", "")
|
||||
testKeyGet(t, "/foobar", "/foo*", "bar")
|
||||
testKeyGet(t, "/foobar", "/foo/*", "")
|
||||
}
|
||||
|
||||
func testKeyMatch2(t *testing.T, key1 string, key2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes := KeyMatch2(key1, key2)
|
||||
t.Logf("%s < %s: %t", key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func testGlobMatch(t *testing.T, key1 string, key2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes, err := GlobMatch(key1, key2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Logf("%s < %s: %t", key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyMatch2(t *testing.T) {
|
||||
testKeyMatch2(t, "/foo", "/foo", true)
|
||||
testKeyMatch2(t, "/foo", "/foo*", true)
|
||||
testKeyMatch2(t, "/foo", "/foo/*", false)
|
||||
testKeyMatch2(t, "/foo/bar", "/foo", false)
|
||||
testKeyMatch2(t, "/foo/bar", "/foo*", false) // different with KeyMatch.
|
||||
testKeyMatch2(t, "/foo/bar", "/foo/*", true)
|
||||
testKeyMatch2(t, "/foobar", "/foo", false)
|
||||
testKeyMatch2(t, "/foobar", "/foo*", false) // different with KeyMatch.
|
||||
testKeyMatch2(t, "/foobar", "/foo/*", false)
|
||||
|
||||
testKeyMatch2(t, "/", "/:resource", false)
|
||||
testKeyMatch2(t, "/resource1", "/:resource", true)
|
||||
testKeyMatch2(t, "/myid", "/:id/using/:resId", false)
|
||||
testKeyMatch2(t, "/myid/using/myresid", "/:id/using/:resId", true)
|
||||
|
||||
testKeyMatch2(t, "/proxy/myid", "/proxy/:id/*", false)
|
||||
testKeyMatch2(t, "/proxy/myid/", "/proxy/:id/*", true)
|
||||
testKeyMatch2(t, "/proxy/myid/res", "/proxy/:id/*", true)
|
||||
testKeyMatch2(t, "/proxy/myid/res/res2", "/proxy/:id/*", true)
|
||||
testKeyMatch2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/*", true)
|
||||
testKeyMatch2(t, "/proxy/", "/proxy/:id/*", false)
|
||||
|
||||
testKeyMatch2(t, "/alice", "/:id", true)
|
||||
testKeyMatch2(t, "/alice/all", "/:id/all", true)
|
||||
testKeyMatch2(t, "/alice", "/:id/all", false)
|
||||
testKeyMatch2(t, "/alice/all", "/:id", false)
|
||||
|
||||
testKeyMatch2(t, "/alice/all", "/:/all", false)
|
||||
}
|
||||
|
||||
func testKeyGet2(t *testing.T, key1 string, key2 string, pathVar string, res string) {
|
||||
t.Helper()
|
||||
myRes := KeyGet2(key1, key2, pathVar)
|
||||
t.Logf(`%s < %s: %s = "%s"`, key1, key2, pathVar, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf(`%s < %s: %s = "%s" supposed to be "%s"`, key1, key2, pathVar, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyGet2(t *testing.T) {
|
||||
testKeyGet2(t, "/foo", "/foo", "id", "")
|
||||
testKeyGet2(t, "/foo", "/foo*", "id", "")
|
||||
testKeyGet2(t, "/foo", "/foo/*", "id", "")
|
||||
testKeyGet2(t, "/foo/bar", "/foo", "id", "")
|
||||
testKeyGet2(t, "/foo/bar", "/foo*", "id", "")
|
||||
testKeyGet2(t, "/foo/bar", "/foo/*", "id", "")
|
||||
testKeyGet2(t, "/foobar", "/foo", "id", "")
|
||||
testKeyGet2(t, "/foobar", "/foo*", "id", "")
|
||||
testKeyGet2(t, "/foobar", "/foo/*", "id", "")
|
||||
|
||||
testKeyGet2(t, "/", "/:resource", "resource", "")
|
||||
testKeyGet2(t, "/resource1", "/:resource", "resource", "resource1")
|
||||
testKeyGet2(t, "/myid", "/:id/using/:resId", "id", "")
|
||||
testKeyGet2(t, "/myid/using/myresid", "/:id/using/:resId", "id", "myid")
|
||||
testKeyGet2(t, "/myid/using/myresid", "/:id/using/:resId", "resId", "myresid")
|
||||
|
||||
testKeyGet2(t, "/proxy/myid", "/proxy/:id/*", "id", "")
|
||||
testKeyGet2(t, "/proxy/myid/", "/proxy/:id/*", "id", "myid")
|
||||
testKeyGet2(t, "/proxy/myid/res", "/proxy/:id/*", "id", "myid")
|
||||
testKeyGet2(t, "/proxy/myid/res/res2", "/proxy/:id/*", "id", "myid")
|
||||
testKeyGet2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/*", "id", "myid")
|
||||
testKeyGet2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id", "myid")
|
||||
testKeyGet2(t, "/proxy/", "/proxy/:id/*", "id", "")
|
||||
|
||||
testKeyGet2(t, "/alice", "/:id", "id", "alice")
|
||||
testKeyGet2(t, "/alice/all", "/:id/all", "id", "alice")
|
||||
testKeyGet2(t, "/alice", "/:id/all", "id", "")
|
||||
testKeyGet2(t, "/alice/all", "/:id", "id", "")
|
||||
|
||||
testKeyGet2(t, "/alice/all", "/:/all", "", "")
|
||||
}
|
||||
|
||||
func testKeyMatch3(t *testing.T, key1 string, key2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes := KeyMatch3(key1, key2)
|
||||
t.Logf("%s < %s: %t", key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyMatch3(t *testing.T) {
|
||||
// keyMatch3() is similar with KeyMatch2(), except using "/proxy/{id}" instead of "/proxy/:id".
|
||||
testKeyMatch3(t, "/foo", "/foo", true)
|
||||
testKeyMatch3(t, "/foo", "/foo*", true)
|
||||
testKeyMatch3(t, "/foo", "/foo/*", false)
|
||||
testKeyMatch3(t, "/foo/bar", "/foo", false)
|
||||
testKeyMatch3(t, "/foo/bar", "/foo*", false)
|
||||
testKeyMatch3(t, "/foo/bar", "/foo/*", true)
|
||||
testKeyMatch3(t, "/foobar", "/foo", false)
|
||||
testKeyMatch3(t, "/foobar", "/foo*", false)
|
||||
testKeyMatch3(t, "/foobar", "/foo/*", false)
|
||||
|
||||
testKeyMatch3(t, "/", "/{resource}", false)
|
||||
testKeyMatch3(t, "/resource1", "/{resource}", true)
|
||||
testKeyMatch3(t, "/myid", "/{id}/using/{resId}", false)
|
||||
testKeyMatch3(t, "/myid/using/myresid", "/{id}/using/{resId}", true)
|
||||
|
||||
testKeyMatch3(t, "/proxy/myid", "/proxy/{id}/*", false)
|
||||
testKeyMatch3(t, "/proxy/myid/", "/proxy/{id}/*", true)
|
||||
testKeyMatch3(t, "/proxy/myid/res", "/proxy/{id}/*", true)
|
||||
testKeyMatch3(t, "/proxy/myid/res/res2", "/proxy/{id}/*", true)
|
||||
testKeyMatch3(t, "/proxy/myid/res/res2/res3", "/proxy/{id}/*", true)
|
||||
testKeyMatch3(t, "/proxy/", "/proxy/{id}/*", false)
|
||||
|
||||
testKeyMatch3(t, "/myid/using/myresid", "/{id/using/{resId}", false)
|
||||
}
|
||||
|
||||
func testKeyGet3(t *testing.T, key1 string, key2 string, pathVar string, res string) {
|
||||
t.Helper()
|
||||
myRes := KeyGet3(key1, key2, pathVar)
|
||||
t.Logf(`%s < %s: %s = "%s"`, key1, key2, pathVar, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf(`%s < %s: %s = "%s" supposed to be "%s"`, key1, key2, pathVar, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyGet3(t *testing.T) {
|
||||
// KeyGet3() is similar with KeyGet2(), except using "/proxy/{id}" instead of "/proxy/:id".
|
||||
testKeyGet3(t, "/foo", "/foo", "id", "")
|
||||
testKeyGet3(t, "/foo", "/foo*", "id", "")
|
||||
testKeyGet3(t, "/foo", "/foo/*", "id", "")
|
||||
testKeyGet3(t, "/foo/bar", "/foo", "id", "")
|
||||
testKeyGet3(t, "/foo/bar", "/foo*", "id", "")
|
||||
testKeyGet3(t, "/foo/bar", "/foo/*", "id", "")
|
||||
testKeyGet3(t, "/foobar", "/foo", "id", "")
|
||||
testKeyGet3(t, "/foobar", "/foo*", "id", "")
|
||||
testKeyGet3(t, "/foobar", "/foo/*", "id", "")
|
||||
|
||||
testKeyGet3(t, "/", "/{resource}", "resource", "")
|
||||
testKeyGet3(t, "/resource1", "/{resource}", "resource", "resource1")
|
||||
testKeyGet3(t, "/myid", "/{id}/using/{resId}", "id", "")
|
||||
testKeyGet3(t, "/myid/using/myresid", "/{id}/using/{resId}", "id", "myid")
|
||||
testKeyGet3(t, "/myid/using/myresid", "/{id}/using/{resId}", "resId", "myresid")
|
||||
|
||||
testKeyGet3(t, "/proxy/myid", "/proxy/{id}/*", "id", "")
|
||||
testKeyGet3(t, "/proxy/myid/", "/proxy/{id}/*", "id", "myid")
|
||||
testKeyGet3(t, "/proxy/myid/res", "/proxy/{id}/*", "id", "myid")
|
||||
testKeyGet3(t, "/proxy/myid/res/res2", "/proxy/{id}/*", "id", "myid")
|
||||
testKeyGet3(t, "/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id", "myid")
|
||||
testKeyGet3(t, "/proxy/", "/proxy/{id}/*", "id", "")
|
||||
|
||||
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{proj}_admin/info",
|
||||
"proj", "")
|
||||
testKeyGet3(t, "/{id/using/myresid", "/{id/using/{resId}", "resId", "myresid")
|
||||
testKeyGet3(t, "/{id/using/myresid/status}", "/{id/using/{resId}/status}", "resId", "myresid")
|
||||
|
||||
testKeyGet3(t, "/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res", "res3")
|
||||
testKeyGet3(t, "/api/project1_admin/info", "/api/{proj}_admin/info", "proj", "project1")
|
||||
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info",
|
||||
"g", "group1")
|
||||
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info",
|
||||
"gn", "group_name")
|
||||
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info",
|
||||
"proj", "project1")
|
||||
}
|
||||
|
||||
func testKeyMatch4(t *testing.T, key1 string, key2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes := KeyMatch4(key1, key2)
|
||||
t.Logf("%s < %s: %t", key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyMatch4(t *testing.T) {
|
||||
testKeyMatch4(t, "/parent/123/child/123", "/parent/{id}/child/{id}", true)
|
||||
testKeyMatch4(t, "/parent/123/child/456", "/parent/{id}/child/{id}", false)
|
||||
|
||||
testKeyMatch4(t, "/parent/123/child/123", "/parent/{id}/child/{another_id}", true)
|
||||
testKeyMatch4(t, "/parent/123/child/456", "/parent/{id}/child/{another_id}", true)
|
||||
|
||||
testKeyMatch4(t, "/parent/123/child/123/book/123", "/parent/{id}/child/{id}/book/{id}", true)
|
||||
testKeyMatch4(t, "/parent/123/child/123/book/456", "/parent/{id}/child/{id}/book/{id}", false)
|
||||
testKeyMatch4(t, "/parent/123/child/456/book/123", "/parent/{id}/child/{id}/book/{id}", false)
|
||||
testKeyMatch4(t, "/parent/123/child/456/book/", "/parent/{id}/child/{id}/book/{id}", false)
|
||||
testKeyMatch4(t, "/parent/123/child/456", "/parent/{id}/child/{id}/book/{id}", false)
|
||||
|
||||
testKeyMatch4(t, "/parent/123/child/123", "/parent/{i/d}/child/{i/d}", false)
|
||||
}
|
||||
|
||||
func testRegexMatch(t *testing.T, key1 string, key2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes := RegexMatch(key1, key2)
|
||||
t.Logf("%s < %s: %t", key1, key2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatch(t *testing.T) {
|
||||
testRegexMatch(t, "/topic/create", "/topic/create", true)
|
||||
testRegexMatch(t, "/topic/create/123", "/topic/create", true)
|
||||
testRegexMatch(t, "/topic/delete", "/topic/create", false)
|
||||
testRegexMatch(t, "/topic/edit", "/topic/edit/[0-9]+", false)
|
||||
testRegexMatch(t, "/topic/edit/123", "/topic/edit/[0-9]+", true)
|
||||
testRegexMatch(t, "/topic/edit/abc", "/topic/edit/[0-9]+", false)
|
||||
testRegexMatch(t, "/foo/delete/123", "/topic/delete/[0-9]+", false)
|
||||
testRegexMatch(t, "/topic/delete/0", "/topic/delete/[0-9]+", true)
|
||||
testRegexMatch(t, "/topic/edit/123s", "/topic/delete/[0-9]+", false)
|
||||
}
|
||||
|
||||
func testIPMatch(t *testing.T, ip1 string, ip2 string, res bool) {
|
||||
t.Helper()
|
||||
myRes := IPMatch(ip1, ip2)
|
||||
t.Logf("%s < %s: %t", ip1, ip2, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", ip1, ip2, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatch(t *testing.T) {
|
||||
testIPMatch(t, "192.168.2.123", "192.168.2.0/24", true)
|
||||
testIPMatch(t, "192.168.2.123", "192.168.3.0/24", false)
|
||||
testIPMatch(t, "192.168.2.123", "192.168.2.0/16", true)
|
||||
testIPMatch(t, "192.168.2.123", "192.168.2.123", true)
|
||||
testIPMatch(t, "192.168.2.123", "192.168.2.123/32", true)
|
||||
testIPMatch(t, "10.0.0.11", "10.0.0.0/8", true)
|
||||
testIPMatch(t, "11.0.0.123", "10.0.0.0/8", false)
|
||||
}
|
||||
|
||||
func testRegexMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := RegexMatchFunc(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyMatchFunc(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyGetFunc(t *testing.T, res interface{}, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyGetFunc(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyMatch2Func(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyMatch2Func(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyGet2Func(t *testing.T, res interface{}, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyGet2Func(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyMatch3Func(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyMatch3Func(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyGet3Func(t *testing.T, res interface{}, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyGet3Func(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyMatch4Func(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyMatch4Func(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyMatch5Func(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := KeyMatch5Func(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testIPMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {
|
||||
t.Helper()
|
||||
myRes, myErr := IPMatchFunc(args...)
|
||||
myErrStr := ""
|
||||
|
||||
if myErr != nil {
|
||||
myErrStr = myErr.Error()
|
||||
}
|
||||
|
||||
if myRes != res || err != myErrStr {
|
||||
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatchFunc(t *testing.T) {
|
||||
testRegexMatchFunc(t, false, "regexMatch: expected 2 arguments, but got 1", "/topic/create")
|
||||
testRegexMatchFunc(t, false, "regexMatch: expected 2 arguments, but got 3", "/topic/create/123", "/topic/create", "/topic/update")
|
||||
testRegexMatchFunc(t, false, "regexMatch: argument must be a string", "/topic/create", false)
|
||||
testRegexMatchFunc(t, true, "", "/topic/create/123", "/topic/create")
|
||||
}
|
||||
|
||||
func TestKeyMatchFunc(t *testing.T) {
|
||||
testKeyMatchFunc(t, false, "keyMatch: expected 2 arguments, but got 1", "/foo")
|
||||
testKeyMatchFunc(t, false, "keyMatch: expected 2 arguments, but got 3", "/foo/create/123", "/foo/*", "/foo/update/123")
|
||||
testKeyMatchFunc(t, false, "keyMatch: argument must be a string", "/foo", true)
|
||||
testKeyMatchFunc(t, false, "", "/foo/bar", "/foo")
|
||||
testKeyMatchFunc(t, true, "", "/foo/bar", "/foo/*")
|
||||
testKeyMatchFunc(t, true, "", "/foo/bar", "/foo*")
|
||||
}
|
||||
|
||||
func TestKeyGetFunc(t *testing.T) {
|
||||
testKeyGetFunc(t, false, "keyGet: expected 2 arguments, but got 1", "/foo")
|
||||
testKeyGetFunc(t, false, "keyGet: expected 2 arguments, but got 3", "/foo/bar", "/foo/*", "bar")
|
||||
testKeyGetFunc(t, false, "keyGet: argument must be a string", "/foo/bar", true)
|
||||
|
||||
testKeyGetFunc(t, "", "", "/foo/bar", "/foo")
|
||||
testKeyGetFunc(t, "bar", "", "/foo/bar", "/foo/*")
|
||||
testKeyGetFunc(t, "/bar", "", "/foo/bar", "/foo*")
|
||||
}
|
||||
|
||||
func TestKeyMatch2Func(t *testing.T) {
|
||||
testKeyMatch2Func(t, false, "keyMatch2: expected 2 arguments, but got 1", "/")
|
||||
testKeyMatch2Func(t, false, "keyMatch2: expected 2 arguments, but got 3", "/foo/create/123", "/*", "/foo/update/123")
|
||||
testKeyMatch2Func(t, false, "keyMatch2: argument must be a string", "/foo", true)
|
||||
|
||||
testKeyMatch2Func(t, false, "", "/", "/:resource")
|
||||
testKeyMatch2Func(t, true, "", "/resource1", "/:resource")
|
||||
|
||||
testKeyMatch2Func(t, true, "", "/foo", "/foo")
|
||||
testKeyMatch2Func(t, true, "", "/foo", "/foo*")
|
||||
testKeyMatch2Func(t, false, "", "/foo", "/foo/*")
|
||||
}
|
||||
|
||||
func TestKeyGet2Func(t *testing.T) {
|
||||
testKeyGet2Func(t, false, "keyGet2: expected 3 arguments, but got 2", "/myid/using/myresid", "/:id/using/:resId")
|
||||
testKeyGet2Func(t, false, "keyGet2: expected 3 arguments, but got 4", "/myid/using/myresid", "/:id/using/:resId", "id", "resId")
|
||||
testKeyGet2Func(t, false, "keyGet2: argument must be a string", "/myid/using/myresid", "/:id/using/:resId", true)
|
||||
|
||||
testKeyGet2Func(t, "myid", "", "/myid/using/myresid", "/:id/using/:resId", "id")
|
||||
testKeyGet2Func(t, "myresid", "", "/myid/using/myresid", "/:id/using/:resId", "resId")
|
||||
testKeyGet2Func(t, "", "", "/myid/using/myresid", "/:id/using/:resId", "missing")
|
||||
}
|
||||
|
||||
func TestKeyMatch3Func(t *testing.T) {
|
||||
testKeyMatch3Func(t, false, "keyMatch3: expected 2 arguments, but got 1", "/")
|
||||
testKeyMatch3Func(t, false, "keyMatch3: expected 2 arguments, but got 3", "/foo/create/123", "/*", "/foo/update/123")
|
||||
testKeyMatch3Func(t, false, "keyMatch3: argument must be a string", "/foo", true)
|
||||
|
||||
testKeyMatch3Func(t, true, "", "/foo", "/foo")
|
||||
testKeyMatch3Func(t, true, "", "/foo", "/foo*")
|
||||
testKeyMatch3Func(t, false, "", "/foo", "/foo/*")
|
||||
testKeyMatch3Func(t, false, "", "/foo/bar", "/foo")
|
||||
testKeyMatch3Func(t, false, "", "/foo/bar", "/foo*")
|
||||
testKeyMatch3Func(t, true, "", "/foo/bar", "/foo/*")
|
||||
testKeyMatch3Func(t, false, "", "/foobar", "/foo")
|
||||
testKeyMatch3Func(t, false, "", "/foobar", "/foo*")
|
||||
testKeyMatch3Func(t, false, "", "/foobar", "/foo/*")
|
||||
|
||||
testKeyMatch3Func(t, false, "", "/", "/{resource}")
|
||||
testKeyMatch3Func(t, true, "", "/resource1", "/{resource}")
|
||||
testKeyMatch3Func(t, false, "", "/myid", "/{id}/using/{resId}")
|
||||
testKeyMatch3Func(t, true, "", "/myid/using/myresid", "/{id}/using/{resId}")
|
||||
|
||||
testKeyMatch3Func(t, false, "", "/proxy/myid", "/proxy/{id}/*")
|
||||
testKeyMatch3Func(t, true, "", "/proxy/myid/", "/proxy/{id}/*")
|
||||
testKeyMatch3Func(t, true, "", "/proxy/myid/res", "/proxy/{id}/*")
|
||||
testKeyMatch3Func(t, true, "", "/proxy/myid/res/res2", "/proxy/{id}/*")
|
||||
testKeyMatch3Func(t, true, "", "/proxy/myid/res/res2/res3", "/proxy/{id}/*")
|
||||
testKeyMatch3Func(t, false, "", "/proxy/", "/proxy/{id}/*")
|
||||
}
|
||||
|
||||
func TestKeyGet3Func(t *testing.T) {
|
||||
testKeyGet3Func(t, false, "keyGet3: expected 3 arguments, but got 2", "/myid/using/myresid", "/{id}/using/{resId}")
|
||||
testKeyGet3Func(t, false, "keyGet3: expected 3 arguments, but got 4", "/myid/using/myresid", "/{id}/using/{resId}", "id", "resId")
|
||||
testKeyGet3Func(t, false, "keyGet3: argument must be a string", "/myid/using/myresid", "/{id}/using/{resId}", true)
|
||||
|
||||
testKeyGet3Func(t, "myid", "", "/myid/using/myresid", "/{id}/using/{resId}", "id")
|
||||
testKeyGet3Func(t, "group_name", "", "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", "gn")
|
||||
testKeyGet3Func(t, "", "", "/myid/using/myresid", "/{id}/using/{resId}", "missing")
|
||||
}
|
||||
|
||||
func TestKeyMatch4Func(t *testing.T) {
|
||||
testKeyMatch4Func(t, false, "keyMatch4: expected 2 arguments, but got 1", "/parent/123/child/123")
|
||||
testKeyMatch4Func(t, false, "keyMatch4: expected 2 arguments, but got 3", "/parent/123/child/123", "/parent/{id}/child/{id}", true)
|
||||
testKeyMatch4Func(t, false, "keyMatch4: argument must be a string", "/parent/123/child/123", true)
|
||||
|
||||
testKeyMatch4Func(t, true, "", "/parent/123/child/123", "/parent/{id}/child/{id}")
|
||||
testKeyMatch4Func(t, false, "", "/parent/123/child/456", "/parent/{id}/child/{id}")
|
||||
|
||||
testKeyMatch4Func(t, true, "", "/parent/123/child/123", "/parent/{id}/child/{another_id}")
|
||||
testKeyMatch4Func(t, true, "", "/parent/123/child/456", "/parent/{id}/child/{another_id}")
|
||||
}
|
||||
|
||||
func TestKeyMatch5Func(t *testing.T) {
|
||||
testKeyMatch5Func(t, false, "keyMatch5: expected 2 arguments, but got 1", "/foo")
|
||||
testKeyMatch5Func(t, false, "keyMatch5: expected 2 arguments, but got 3", "/foo/create/123", "/foo/*", "/foo/update/123")
|
||||
testKeyMatch5Func(t, false, "keyMatch5: argument must be a string", "/parent/123", true)
|
||||
|
||||
testKeyMatch5Func(t, true, "", "/parent/child?status=1&type=2", "/parent/child")
|
||||
testKeyMatch5Func(t, false, "", "/parent?status=1&type=2", "/parent/child")
|
||||
|
||||
testKeyMatch5Func(t, true, "", "/parent/child/?status=1&type=2", "/parent/child/")
|
||||
testKeyMatch5Func(t, false, "", "/parent/child/?status=1&type=2", "/parent/child")
|
||||
testKeyMatch5Func(t, false, "", "/parent/child?status=1&type=2", "/parent/child/")
|
||||
|
||||
testKeyMatch5Func(t, true, "", "/foo", "/foo")
|
||||
testKeyMatch5Func(t, true, "", "/foo", "/foo*")
|
||||
testKeyMatch5Func(t, false, "", "/foo", "/foo/*")
|
||||
testKeyMatch5Func(t, false, "", "/foo/bar", "/foo")
|
||||
testKeyMatch5Func(t, false, "", "/foo/bar", "/foo*")
|
||||
testKeyMatch5Func(t, true, "", "/foo/bar", "/foo/*")
|
||||
testKeyMatch5Func(t, false, "", "/foobar", "/foo")
|
||||
testKeyMatch5Func(t, false, "", "/foobar", "/foo*")
|
||||
testKeyMatch5Func(t, false, "", "/foobar", "/foo/*")
|
||||
|
||||
testKeyMatch5Func(t, false, "", "/", "/{resource}")
|
||||
testKeyMatch5Func(t, true, "", "/resource1", "/{resource}")
|
||||
testKeyMatch5Func(t, false, "", "/myid", "/{id}/using/{resId}")
|
||||
testKeyMatch5Func(t, true, "", "/myid/using/myresid", "/{id}/using/{resId}")
|
||||
|
||||
testKeyMatch5Func(t, false, "", "/proxy/myid", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/res", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2/res3", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, false, "", "/proxy/", "/proxy/{id}/*")
|
||||
|
||||
testKeyMatch5Func(t, false, "", "/proxy/myid?status=1&type=2", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/res?status=1&type=2", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2?status=1&type=2", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2/res3?status=1&type=2", "/proxy/{id}/*")
|
||||
testKeyMatch5Func(t, false, "", "/proxy/", "/proxy/{id}/*")
|
||||
}
|
||||
|
||||
func TestIPMatchFunc(t *testing.T) {
|
||||
testIPMatchFunc(t, false, "ipMatch: expected 2 arguments, but got 1", "192.168.2.123")
|
||||
testIPMatchFunc(t, false, "ipMatch: argument must be a string", "192.168.2.123", 128)
|
||||
testIPMatchFunc(t, true, "", "192.168.2.123", "192.168.2.0/24")
|
||||
}
|
||||
|
||||
func TestGlobMatch(t *testing.T) {
|
||||
testGlobMatch(t, "/foo", "/foo", true)
|
||||
testGlobMatch(t, "/foo", "/foo*", true)
|
||||
testGlobMatch(t, "/foo", "/foo/*", false)
|
||||
testGlobMatch(t, "/foo/bar", "/foo", false)
|
||||
testGlobMatch(t, "/foo/bar", "/foo*", false)
|
||||
testGlobMatch(t, "/foo/bar", "/foo/*", true)
|
||||
testGlobMatch(t, "/foobar", "/foo", false)
|
||||
testGlobMatch(t, "/foobar", "/foo*", true)
|
||||
testGlobMatch(t, "/foobar", "/foo/*", false)
|
||||
|
||||
testGlobMatch(t, "/foo", "*/foo", true)
|
||||
testGlobMatch(t, "/foo", "*/foo*", true)
|
||||
testGlobMatch(t, "/foo", "*/foo/*", false)
|
||||
testGlobMatch(t, "/foo/bar", "*/foo", false)
|
||||
testGlobMatch(t, "/foo/bar", "*/foo*", false)
|
||||
testGlobMatch(t, "/foo/bar", "*/foo/*", true)
|
||||
testGlobMatch(t, "/foobar", "*/foo", false)
|
||||
testGlobMatch(t, "/foobar", "*/foo*", true)
|
||||
testGlobMatch(t, "/foobar", "*/foo/*", false)
|
||||
|
||||
testGlobMatch(t, "/prefix/foo", "*/foo", false)
|
||||
testGlobMatch(t, "/prefix/foo", "*/foo*", false)
|
||||
testGlobMatch(t, "/prefix/foo", "*/foo/*", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "*/foo", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "*/foo*", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "*/foo/*", false)
|
||||
testGlobMatch(t, "/prefix/foobar", "*/foo", false)
|
||||
testGlobMatch(t, "/prefix/foobar", "*/foo*", false)
|
||||
testGlobMatch(t, "/prefix/foobar", "*/foo/*", false)
|
||||
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo/*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo/*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo/*", false)
|
||||
|
||||
testGlobMatch(t, "/foo", "**/foo", true)
|
||||
testGlobMatch(t, "/foo", "**/foo**", true)
|
||||
testGlobMatch(t, "/foo", "**/foo/**", true)
|
||||
testGlobMatch(t, "/foo/bar", "**/foo", false)
|
||||
testGlobMatch(t, "/foo/bar", "**/foo**", false)
|
||||
testGlobMatch(t, "/foo/bar", "**/foo/**", true)
|
||||
testGlobMatch(t, "/foobar", "**/foo", false)
|
||||
testGlobMatch(t, "/foobar", "**/foo**", true)
|
||||
testGlobMatch(t, "/foobar", "**/foo/**", false)
|
||||
|
||||
testGlobMatch(t, "/prefix/foo", "**/foo", true)
|
||||
testGlobMatch(t, "/prefix/foo", "**/foo**", true)
|
||||
testGlobMatch(t, "/prefix/foo", "**/foo/**", true)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "**/foo", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "**/foo**", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "**/foo/**", true)
|
||||
testGlobMatch(t, "/prefix/foobar", "**/foo", false)
|
||||
testGlobMatch(t, "/prefix/foobar", "**/foo**", true)
|
||||
testGlobMatch(t, "/prefix/foobar", "**/foo/**", false)
|
||||
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo**", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo/**", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo/**", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo**", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo/**", false)
|
||||
|
||||
testGlobMatch(t, "/foo", "*/foo**", true)
|
||||
testGlobMatch(t, "/foo", "**/foo*", true)
|
||||
testGlobMatch(t, "/foo", "*/foo/**", true)
|
||||
testGlobMatch(t, "/foo", "**/foo/*", false)
|
||||
testGlobMatch(t, "/foo/bar", "*/foo**", false)
|
||||
testGlobMatch(t, "/foo/bar", "**/foo*", false)
|
||||
testGlobMatch(t, "/foo/bar", "*/foo/**", true)
|
||||
testGlobMatch(t, "/foo/bar", "**/foo/*", true)
|
||||
testGlobMatch(t, "/foobar", "*/foo**", true)
|
||||
testGlobMatch(t, "/foobar", "**/foo*", true)
|
||||
testGlobMatch(t, "/foobar", "*/foo/**", false)
|
||||
testGlobMatch(t, "/foobar", "**/foo/*", false)
|
||||
|
||||
testGlobMatch(t, "/prefix/foo", "*/foo**", false)
|
||||
testGlobMatch(t, "/prefix/foo", "**/foo*", true)
|
||||
testGlobMatch(t, "/prefix/foo", "*/foo/**", false)
|
||||
testGlobMatch(t, "/prefix/foo", "**/foo/*", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "*/foo**", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "**/foo*", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "*/foo/**", false)
|
||||
testGlobMatch(t, "/prefix/foo/bar", "**/foo/*", true)
|
||||
testGlobMatch(t, "/prefix/foobar", "*/foo**", false)
|
||||
testGlobMatch(t, "/prefix/foobar", "**/foo*", true)
|
||||
testGlobMatch(t, "/prefix/foobar", "*/foo/**", false)
|
||||
testGlobMatch(t, "/prefix/foobar", "**/foo/*", false)
|
||||
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo*", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo/**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo/*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo*", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo/**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo/*", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo*", true)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo/**", false)
|
||||
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo/*", false)
|
||||
}
|
||||
|
||||
func testTimeMatch(t *testing.T, startTime string, endTime string, res bool) {
|
||||
t.Helper()
|
||||
myRes, err := TimeMatch(startTime, endTime)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Logf("%s < %s: %t", startTime, endTime, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s < %s: %t, supposed to be %t", startTime, endTime, !res, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestMatch(t *testing.T) {
|
||||
testTimeMatch(t, "0000-01-01 00:00:00", "0000-01-02 00:00:00", false)
|
||||
testTimeMatch(t, "0000-01-01 00:00:00", "9999-12-30 00:00:00", true)
|
||||
testTimeMatch(t, "_", "_", true)
|
||||
testTimeMatch(t, "_", "9999-12-30 00:00:00", true)
|
||||
testTimeMatch(t, "_", "0000-01-02 00:00:00", false)
|
||||
testTimeMatch(t, "0000-01-01 00:00:00", "_", true)
|
||||
testTimeMatch(t, "9999-12-30 00:00:00", "_", false)
|
||||
}
|
||||
441
util/util.go
Normal file
441
util/util.go
Normal file
@@ -0,0 +1,441 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var evalReg = regexp.MustCompile(`\beval\((?P<rule>[^)]*)\)`)
|
||||
|
||||
var escapeAssertionRegex = regexp.MustCompile(`([()\s|&,=!><+\-*/]|^)((r|p)[0-9]*)\.`)
|
||||
|
||||
func JsonToMap(jsonStr string) (map[string]interface{}, error) {
|
||||
result := make(map[string]interface{})
|
||||
err := json.Unmarshal([]byte(jsonStr), &result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EscapeAssertion escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.
|
||||
func EscapeAssertion(s string) string {
|
||||
s = escapeAssertionRegex.ReplaceAllStringFunc(s, func(m string) string {
|
||||
// Replace only the last dot with underscore (preserve the prefix character)
|
||||
lastDotIdx := strings.LastIndex(m, ".")
|
||||
if lastDotIdx > 0 {
|
||||
return m[:lastDotIdx] + "_"
|
||||
}
|
||||
return m
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
// RemoveComments removes the comments starting with # in the text.
|
||||
func RemoveComments(s string) string {
|
||||
pos := strings.Index(s, "#")
|
||||
if pos == -1 {
|
||||
return s
|
||||
}
|
||||
return strings.TrimSpace(s[0:pos])
|
||||
}
|
||||
|
||||
// ArrayEquals determines whether two string arrays are identical.
|
||||
func ArrayEquals(a []string, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Array2DEquals determines whether two 2-dimensional string arrays are identical.
|
||||
func Array2DEquals(a [][]string, b [][]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if !ArrayEquals(v, b[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SortArray2D Sorts the two-dimensional string array.
|
||||
func SortArray2D(arr [][]string) {
|
||||
if len(arr) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
minArrLen := len(arr[i])
|
||||
if len(arr[j]) < minArrLen {
|
||||
minArrLen = len(arr[j])
|
||||
}
|
||||
for k := 0; k < minArrLen; k++ {
|
||||
if arr[i][k] != arr[j][k] {
|
||||
return arr[i][k] < arr[j][k]
|
||||
}
|
||||
}
|
||||
return len(arr[i]) < len(arr[j])
|
||||
})
|
||||
}
|
||||
|
||||
// SortedArray2DEquals determines whether two 2-dimensional string arrays are identical.
|
||||
func SortedArray2DEquals(a [][]string, b [][]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
copyA := make([][]string, len(a))
|
||||
copy(copyA, a)
|
||||
copyB := make([][]string, len(b))
|
||||
copy(copyB, b)
|
||||
|
||||
SortArray2D(copyA)
|
||||
SortArray2D(copyB)
|
||||
|
||||
for i, v := range copyA {
|
||||
if !ArrayEquals(v, copyB[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ArrayRemoveDuplicates removes any duplicated elements in a string array.
|
||||
func ArrayRemoveDuplicates(s *[]string) {
|
||||
found := make(map[string]bool)
|
||||
j := 0
|
||||
for i, x := range *s {
|
||||
if !found[x] {
|
||||
found[x] = true
|
||||
(*s)[j] = (*s)[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
*s = (*s)[:j]
|
||||
}
|
||||
|
||||
// ArrayToString gets a printable string for a string array.
|
||||
func ArrayToString(s []string) string {
|
||||
return strings.Join(s, ", ")
|
||||
}
|
||||
|
||||
// ParamsToString gets a printable string for variable number of parameters.
|
||||
func ParamsToString(s ...string) string {
|
||||
return strings.Join(s, ", ")
|
||||
}
|
||||
|
||||
// SetEquals determines whether two string sets are identical.
|
||||
func SetEquals(a []string, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
count := make(map[string]int, len(a))
|
||||
for _, v := range a {
|
||||
count[v]++
|
||||
}
|
||||
for _, v := range b {
|
||||
count[v]--
|
||||
if count[v] < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// SetEqualsInt determines whether two int sets are identical.
|
||||
func SetEqualsInt(a []int, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
count := make(map[int]int, len(a))
|
||||
for _, v := range a {
|
||||
count[v]++
|
||||
}
|
||||
for _, v := range b {
|
||||
count[v]--
|
||||
if count[v] < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Set2DEquals determines whether two string slice sets are identical.
|
||||
func Set2DEquals(a [][]string, b [][]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
return SetEquals(setKeys(a), setKeys(b))
|
||||
}
|
||||
|
||||
// setKeys maps every row to an order-independent key, without touching the row.
|
||||
// The fields are joined on a NUL because a policy value may contain a comma.
|
||||
func setKeys(rows [][]string) []string {
|
||||
keys := make([]string, len(rows))
|
||||
|
||||
for i, row := range rows {
|
||||
fields := make([]string, len(row))
|
||||
copy(fields, row)
|
||||
sort.Strings(fields)
|
||||
keys[i] = strings.Join(fields, "\x00")
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// JoinSlice joins a string and a slice into a new slice.
|
||||
func JoinSlice(a string, b ...string) []string {
|
||||
res := make([]string, 0, len(b)+1)
|
||||
|
||||
res = append(res, a)
|
||||
res = append(res, b...)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// JoinSliceAny joins a string and a slice into a new interface{} slice.
|
||||
func JoinSliceAny(a string, b ...string) []interface{} {
|
||||
res := make([]interface{}, 0, len(b)+1)
|
||||
|
||||
res = append(res, a)
|
||||
for _, s := range b {
|
||||
res = append(res, s)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// SetSubtract returns the elements in `a` that aren't in `b`.
|
||||
func SetSubtract(a []string, b []string) []string {
|
||||
mb := make(map[string]struct{}, len(b))
|
||||
for _, x := range b {
|
||||
mb[x] = struct{}{}
|
||||
}
|
||||
var diff []string
|
||||
for _, x := range a {
|
||||
if _, found := mb[x]; !found {
|
||||
diff = append(diff, x)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// HasEval determine whether matcher contains function eval.
|
||||
func HasEval(s string) bool {
|
||||
return evalReg.MatchString(s)
|
||||
}
|
||||
|
||||
// ReplaceEval replace function eval with the value of its parameters.
|
||||
func ReplaceEval(s string, rule string) string {
|
||||
return evalReg.ReplaceAllString(s, "("+rule+")")
|
||||
}
|
||||
|
||||
// ReplaceEvalWithMap replace function eval with the value of its parameters via given sets.
|
||||
func ReplaceEvalWithMap(src string, sets map[string]string) string {
|
||||
return evalReg.ReplaceAllStringFunc(src, func(s string) string {
|
||||
subs := evalReg.FindStringSubmatch(s)
|
||||
if subs == nil {
|
||||
return s
|
||||
}
|
||||
key := subs[1]
|
||||
value, found := sets[key]
|
||||
if !found {
|
||||
return s
|
||||
}
|
||||
return evalReg.ReplaceAllString(s, value)
|
||||
})
|
||||
}
|
||||
|
||||
// GetEvalValue returns the parameters of function eval.
|
||||
func GetEvalValue(s string) []string {
|
||||
subMatch := evalReg.FindAllStringSubmatch(s, -1)
|
||||
var rules []string
|
||||
for _, rule := range subMatch {
|
||||
rules = append(rules, rule[1])
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// EscapeStringLiterals escapes backslashes in string literals within an expression
|
||||
// to ensure consistent handling between govaluate (which interprets escape sequences)
|
||||
// and CSV parsing (which treats backslashes as literal characters).
|
||||
// This function doubles all backslashes within single-quoted and double-quoted strings.
|
||||
func EscapeStringLiterals(expr string) string {
|
||||
var result strings.Builder
|
||||
inString := false
|
||||
var quote rune
|
||||
|
||||
for _, ch := range expr {
|
||||
if inString {
|
||||
result.WriteRune(ch)
|
||||
switch ch {
|
||||
case '\\':
|
||||
// Found a backslash inside a string - double it
|
||||
result.WriteRune('\\')
|
||||
case quote:
|
||||
// End of string literal
|
||||
inString = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Not inside a string literal
|
||||
if ch == '\'' || ch == '"' {
|
||||
inString = true
|
||||
quote = ch
|
||||
}
|
||||
result.WriteRune(ch)
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func RemoveDuplicateElement(s []string) []string {
|
||||
result := make([]string, 0, len(s))
|
||||
temp := map[string]struct{}{}
|
||||
for _, item := range s {
|
||||
if _, ok := temp[item]; !ok {
|
||||
temp[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type node struct {
|
||||
key interface{}
|
||||
value interface{}
|
||||
prev *node
|
||||
next *node
|
||||
}
|
||||
|
||||
type LRUCache struct {
|
||||
capacity int
|
||||
m map[interface{}]*node
|
||||
head *node
|
||||
tail *node
|
||||
}
|
||||
|
||||
func NewLRUCache(capacity int) *LRUCache {
|
||||
cache := &LRUCache{}
|
||||
cache.capacity = capacity
|
||||
cache.m = map[interface{}]*node{}
|
||||
|
||||
head := &node{}
|
||||
tail := &node{}
|
||||
|
||||
head.next = tail
|
||||
tail.prev = head
|
||||
|
||||
cache.head = head
|
||||
cache.tail = tail
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (cache *LRUCache) remove(n *node, listOnly bool) {
|
||||
if !listOnly {
|
||||
delete(cache.m, n.key)
|
||||
}
|
||||
n.prev.next = n.next
|
||||
n.next.prev = n.prev
|
||||
}
|
||||
|
||||
func (cache *LRUCache) add(n *node, listOnly bool) {
|
||||
if !listOnly {
|
||||
cache.m[n.key] = n
|
||||
}
|
||||
headNext := cache.head.next
|
||||
cache.head.next = n
|
||||
headNext.prev = n
|
||||
n.next = headNext
|
||||
n.prev = cache.head
|
||||
}
|
||||
|
||||
func (cache *LRUCache) moveToHead(n *node) {
|
||||
cache.remove(n, true)
|
||||
cache.add(n, true)
|
||||
}
|
||||
|
||||
func (cache *LRUCache) Get(key interface{}) (value interface{}, ok bool) {
|
||||
n, ok := cache.m[key]
|
||||
if ok {
|
||||
cache.moveToHead(n)
|
||||
return n.value, ok
|
||||
} else {
|
||||
return nil, ok
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *LRUCache) Put(key interface{}, value interface{}) {
|
||||
if cache.capacity <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
n, ok := cache.m[key]
|
||||
if ok {
|
||||
cache.remove(n, false)
|
||||
} else {
|
||||
n = &node{key, value, nil, nil}
|
||||
if len(cache.m) >= cache.capacity {
|
||||
cache.remove(cache.tail.prev, false)
|
||||
}
|
||||
}
|
||||
cache.add(n, false)
|
||||
}
|
||||
|
||||
type SyncLRUCache struct {
|
||||
rwm sync.RWMutex
|
||||
*LRUCache
|
||||
}
|
||||
|
||||
func NewSyncLRUCache(capacity int) *SyncLRUCache {
|
||||
cache := &SyncLRUCache{}
|
||||
cache.LRUCache = NewLRUCache(capacity)
|
||||
return cache
|
||||
}
|
||||
|
||||
func (cache *SyncLRUCache) Get(key interface{}) (value interface{}, ok bool) {
|
||||
cache.rwm.Lock()
|
||||
defer cache.rwm.Unlock()
|
||||
return cache.LRUCache.Get(key)
|
||||
}
|
||||
|
||||
func (cache *SyncLRUCache) Put(key interface{}, value interface{}) {
|
||||
cache.rwm.Lock()
|
||||
defer cache.rwm.Unlock()
|
||||
cache.LRUCache.Put(key, value)
|
||||
}
|
||||
281
util/util_test.go
Normal file
281
util/util_test.go
Normal file
@@ -0,0 +1,281 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testEscapeAssertion(t *testing.T, s string, res string) {
|
||||
t.Helper()
|
||||
myRes := EscapeAssertion(s)
|
||||
t.Logf("%s: %s", s, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s: %s, supposed to be %s", s, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeAssertion(t *testing.T) {
|
||||
testEscapeAssertion(t, "r_sub == r_obj.value", "r_sub == r_obj.value")
|
||||
testEscapeAssertion(t, "p_sub == r_sub.value", "p_sub == r_sub.value")
|
||||
testEscapeAssertion(t, "r.attr.value == p.attr", "r_attr.value == p_attr")
|
||||
testEscapeAssertion(t, "r.attr.value == p.attr", "r_attr.value == p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value || p.attr", "r_attp.value || p_attr")
|
||||
testEscapeAssertion(t, "r2.attr.value == p2.attr", "r2_attr.value == p2_attr")
|
||||
testEscapeAssertion(t, "r2.attp.value || p2.attr", "r2_attp.value || p2_attr")
|
||||
testEscapeAssertion(t, "r.attp.value &&p.attr", "r_attp.value &&p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value >p.attr", "r_attp.value >p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value <p.attr", "r_attp.value <p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value +p.attr", "r_attp.value +p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value -p.attr", "r_attp.value -p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value *p.attr", "r_attp.value *p_attr")
|
||||
testEscapeAssertion(t, "r.attp.value /p.attr", "r_attp.value /p_attr")
|
||||
testEscapeAssertion(t, "!r.attp.value /p.attr", "!r_attp.value /p_attr")
|
||||
testEscapeAssertion(t, "g(r.sub, p.sub) == p.attr", "g(r_sub, p_sub) == p_attr")
|
||||
testEscapeAssertion(t, "g(r.sub,p.sub) == p.attr", "g(r_sub,p_sub) == p_attr")
|
||||
testEscapeAssertion(t, "(r.attp.value || p.attr)p.u", "(r_attp.value || p_attr)p_u")
|
||||
// Test that patterns inside strings are not escaped
|
||||
testEscapeAssertion(t, `r.sub == "a.p.p.l.e"`, `r_sub == "a.p.p.l.e"`)
|
||||
testEscapeAssertion(t, `r.sub == "test.p.value"`, `r_sub == "test.p.value"`)
|
||||
}
|
||||
|
||||
func testRemoveComments(t *testing.T, s string, res string) {
|
||||
t.Helper()
|
||||
myRes := RemoveComments(s)
|
||||
t.Logf("%s: %s", s, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s: %s, supposed to be %s", s, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveComments(t *testing.T) {
|
||||
testRemoveComments(t, "r.act == p.act # comments", "r.act == p.act")
|
||||
testRemoveComments(t, "r.act == p.act#comments", "r.act == p.act")
|
||||
testRemoveComments(t, "r.act == p.act###", "r.act == p.act")
|
||||
testRemoveComments(t, "### comments", "")
|
||||
testRemoveComments(t, "r.act == p.act", "r.act == p.act")
|
||||
}
|
||||
|
||||
func testArrayEquals(t *testing.T, a []string, b []string, res bool) {
|
||||
t.Helper()
|
||||
myRes := ArrayEquals(a, b)
|
||||
t.Logf("%s == %s: %t", a, b, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s == %s: %t, supposed to be %t", a, b, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayEquals(t *testing.T) {
|
||||
testArrayEquals(t, []string{"a", "b", "c"}, []string{"a", "b", "c"}, true)
|
||||
testArrayEquals(t, []string{"a", "b", "c"}, []string{"a", "b"}, false)
|
||||
testArrayEquals(t, []string{"a", "b", "c"}, []string{"a", "c", "b"}, false)
|
||||
testArrayEquals(t, []string{"a", "b", "c"}, []string{}, false)
|
||||
}
|
||||
|
||||
func testArray2DEquals(t *testing.T, a [][]string, b [][]string, res bool) {
|
||||
t.Helper()
|
||||
myRes := Array2DEquals(a, b)
|
||||
t.Logf("%s == %s: %t", a, b, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s == %s: %t, supposed to be %t", a, b, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArray2DEquals(t *testing.T) {
|
||||
testArray2DEquals(t, [][]string{{"a", "b", "c"}, {"1", "2", "3"}}, [][]string{{"a", "b", "c"}, {"1", "2", "3"}}, true)
|
||||
testArray2DEquals(t, [][]string{{"a", "b", "c"}, {"1", "2", "3"}}, [][]string{{"a", "b", "c"}}, false)
|
||||
testArray2DEquals(t, [][]string{{"a", "b", "c"}, {"1", "2", "3"}}, [][]string{{"a", "b", "c"}, {"1", "2"}}, false)
|
||||
testArray2DEquals(t, [][]string{{"a", "b", "c"}, {"1", "2", "3"}}, [][]string{{"1", "2", "3"}, {"a", "b", "c"}}, false)
|
||||
testArray2DEquals(t, [][]string{{"a", "b", "c"}, {"1", "2", "3"}}, [][]string{}, false)
|
||||
}
|
||||
|
||||
func testSetEquals(t *testing.T, a []string, b []string, res bool) {
|
||||
t.Helper()
|
||||
myRes := SetEquals(a, b)
|
||||
t.Logf("%s == %s: %t", a, b, myRes)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s == %s: %t, supposed to be %t", a, b, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEquals(t *testing.T) {
|
||||
testSetEquals(t, []string{"a", "b", "c"}, []string{"a", "b", "c"}, true)
|
||||
testSetEquals(t, []string{"a", "b", "c"}, []string{"a", "b"}, false)
|
||||
testSetEquals(t, []string{"a", "b", "c"}, []string{"a", "c", "b"}, true)
|
||||
testSetEquals(t, []string{"a", "b", "c"}, []string{}, false)
|
||||
}
|
||||
|
||||
func testContainEval(t *testing.T, s string, res bool) {
|
||||
t.Helper()
|
||||
myRes := HasEval(s)
|
||||
if myRes != res {
|
||||
t.Errorf("%s: %t, supposed to be %t", s, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainEval(t *testing.T) {
|
||||
testContainEval(t, "eval() && a && b && c", true)
|
||||
testContainEval(t, "eval) && a && b && c", false)
|
||||
testContainEval(t, "eval)( && a && b && c", false)
|
||||
testContainEval(t, "eval(c * (a + b)) && a && b && c", true)
|
||||
testContainEval(t, "xeval() && a && b && c", false)
|
||||
}
|
||||
|
||||
func testReplaceEval(t *testing.T, s string, rule string, res string) {
|
||||
t.Helper()
|
||||
myRes := ReplaceEval(s, rule)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s: %s supposed to be %s", s, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceEval(t *testing.T) {
|
||||
testReplaceEval(t, "eval() && a && b && c", "a", "(a) && a && b && c")
|
||||
testReplaceEval(t, "eval() && a && b && c", "(a)", "((a)) && a && b && c")
|
||||
}
|
||||
|
||||
func testGetEvalValue(t *testing.T, s string, res []string) {
|
||||
t.Helper()
|
||||
myRes := GetEvalValue(s)
|
||||
|
||||
if !ArrayEquals(myRes, res) {
|
||||
t.Errorf("%s: %s supposed to be %s", s, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEvalValue(t *testing.T) {
|
||||
testGetEvalValue(t, "eval(a) && a && b && c", []string{"a"})
|
||||
testGetEvalValue(t, "a && eval(a) && b && c", []string{"a"})
|
||||
testGetEvalValue(t, "eval(a) && eval(b) && a && b && c", []string{"a", "b"})
|
||||
testGetEvalValue(t, "a && eval(a) && eval(b) && b && c", []string{"a", "b"})
|
||||
}
|
||||
|
||||
func testReplaceEvalWithMap(t *testing.T, s string, sets map[string]string, res string) {
|
||||
t.Helper()
|
||||
myRes := ReplaceEvalWithMap(s, sets)
|
||||
|
||||
if myRes != res {
|
||||
t.Errorf("%s: %s supposed to be %s", s, myRes, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceEvalWithMap(t *testing.T) {
|
||||
testReplaceEvalWithMap(t, "eval(rule1)", map[string]string{"rule1": "a == b"}, "a == b")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) && c && d", map[string]string{"rule1": "a == b"}, "a == b && c && d")
|
||||
testReplaceEvalWithMap(t, "eval(rule1)", nil, "eval(rule1)")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) && c && d", nil, "eval(rule1) && c && d")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", map[string]string{"rule1": "a == b", "rule2": "a == c"}, "a == b || a == c")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", map[string]string{"rule1": "a == b", "rule2": "a == c"}, "a == b || a == c && c && d")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", map[string]string{"rule1": "a == b"}, "a == b || eval(rule2)")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", map[string]string{"rule1": "a == b"}, "a == b || eval(rule2) && c && d")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", map[string]string{"rule2": "a == b"}, "eval(rule1) || a == b")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", map[string]string{"rule2": "a == b"}, "eval(rule1) || a == b && c && d")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", nil, "eval(rule1) || eval(rule2)")
|
||||
testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", nil, "eval(rule1) || eval(rule2) && c && d")
|
||||
}
|
||||
|
||||
func testCacheGet(t *testing.T, c *LRUCache, key string, value interface{}, ok bool) {
|
||||
v, o := c.Get(key)
|
||||
if v != value || o != ok {
|
||||
t.Errorf("Get(%s): (%s, %t) supposed to be (%s, %t)", key, v, o, value, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func testCachePut(t *testing.T, c *LRUCache, key string, value interface{}) {
|
||||
c.Put(key, value)
|
||||
v, o := c.Get(key)
|
||||
if v != value || o != true {
|
||||
t.Errorf("Put(%s, %s): didn't add value", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func testCacheEqual(t *testing.T, c *LRUCache, values []int) {
|
||||
cacheValues := []int{}
|
||||
for _, v := range c.m {
|
||||
cacheValues = append(cacheValues, v.value.(int))
|
||||
}
|
||||
|
||||
if SetEqualsInt(values, cacheValues) == false {
|
||||
t.Errorf("cache values: %d supposed to be %d", cacheValues, values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLRUCache(t *testing.T) {
|
||||
cache := NewLRUCache(3)
|
||||
testCachePut(t, cache, "one", 1)
|
||||
testCachePut(t, cache, "two", 2)
|
||||
testCacheGet(t, cache, "one", 1, true)
|
||||
testCachePut(t, cache, "three", 3)
|
||||
testCachePut(t, cache, "four", 4)
|
||||
testCacheGet(t, cache, "two", nil, false)
|
||||
testCacheEqual(t, cache, []int{1, 3, 4})
|
||||
}
|
||||
|
||||
func testEscapeStringLiterals(t *testing.T, input string, expected string) {
|
||||
t.Helper()
|
||||
result := EscapeStringLiterals(input)
|
||||
t.Logf("Input: %q", input)
|
||||
t.Logf("Expected: %q", expected)
|
||||
t.Logf("Got: %q", result)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("EscapeStringLiterals(%q) = %q, expected %q", input, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeStringLiterals(t *testing.T) {
|
||||
// Test single-quoted strings
|
||||
testEscapeStringLiterals(t, `'\1\2'`, `'\\1\\2'`)
|
||||
testEscapeStringLiterals(t, `'\n\t'`, `'\\n\\t'`)
|
||||
testEscapeStringLiterals(t, `'\\already\\escaped'`, `'\\\\already\\\\escaped'`)
|
||||
|
||||
// Test double-quoted strings
|
||||
testEscapeStringLiterals(t, `"\1\2"`, `"\\1\\2"`)
|
||||
testEscapeStringLiterals(t, `"\n\t"`, `"\\n\\t"`)
|
||||
|
||||
// Test expressions with string literals
|
||||
testEscapeStringLiterals(t, `regexMatch('\1\2', p.obj)`, `regexMatch('\\1\\2', p.obj)`)
|
||||
testEscapeStringLiterals(t, `regexMatch("\1\2", p.obj)`, `regexMatch("\\1\\2", p.obj)`)
|
||||
testEscapeStringLiterals(t, `r.sub == '\test'`, `r.sub == '\\test'`)
|
||||
|
||||
// Test that non-ASCII (multi-byte UTF-8) string literals are preserved
|
||||
testEscapeStringLiterals(t, `r.sub == '香港'`, `r.sub == '香港'`)
|
||||
testEscapeStringLiterals(t, `r.obj == "café"`, `r.obj == "café"`)
|
||||
testEscapeStringLiterals(t, `keyMatch(r.obj, 'naïve\1')`, `keyMatch(r.obj, 'naïve\\1')`)
|
||||
|
||||
// Test expressions without string literals
|
||||
testEscapeStringLiterals(t, `r.sub == p.sub`, `r.sub == p.sub`)
|
||||
testEscapeStringLiterals(t, `keyMatch(r.obj, p.obj)`, `keyMatch(r.obj, p.obj)`)
|
||||
|
||||
// Test multiple strings in one expression
|
||||
testEscapeStringLiterals(t, `regexMatch('\1', '\2')`, `regexMatch('\\1', '\\2')`)
|
||||
|
||||
// Test empty strings
|
||||
testEscapeStringLiterals(t, `''`, `''`)
|
||||
testEscapeStringLiterals(t, `""`, `""`)
|
||||
|
||||
// Test strings with no backslashes
|
||||
testEscapeStringLiterals(t, `'hello'`, `'hello'`)
|
||||
testEscapeStringLiterals(t, `"world"`, `"world"`)
|
||||
}
|
||||
Reference in New Issue
Block a user