mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-28 11:15:32 -06:00
topdown: Add new regex.is_valid built-in function
The existing regex functions error if the pattern is invalid. Until OPA exposes a way to treat built-in function errors as undefined, it is useful to be able to check for validity. Also, some use cases may simply want to verify a regex pattern from the outside. Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
@@ -87,6 +87,7 @@ var DefaultBuiltins = [...]*Builtin{
|
||||
CastArray,
|
||||
|
||||
// Regular Expressions
|
||||
RegexIsValid,
|
||||
RegexMatch,
|
||||
RegexSplit,
|
||||
GlobsMatch,
|
||||
@@ -678,6 +679,17 @@ var RegexMatch = &Builtin{
|
||||
),
|
||||
}
|
||||
|
||||
// RegexIsValid returns true if the regex pattern string is valid, otherwise false.
|
||||
var RegexIsValid = &Builtin{
|
||||
Name: "regex.is_valid",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.S,
|
||||
),
|
||||
types.B,
|
||||
),
|
||||
}
|
||||
|
||||
// RegexFindAllStringSubmatch returns an array of all successive matches of the expression.
|
||||
// It takes two strings and a number, the pattern, the value and number of matches to
|
||||
// return, -1 means all matches.
|
||||
|
||||
@@ -362,6 +362,7 @@ complex types.
|
||||
| Built-in | Description |
|
||||
| ------- |-------------|
|
||||
| <span class="opa-keep-it-together">``re_match(pattern, value)``</span> | true if the ``value`` matches the regex ``pattern`` |
|
||||
| <span class="opa-keep-it-together">``output := regex.is_valid(pattern)``</span> | ``output`` is a ``boolean`` that indicates if ``pattern` is a valid regex pattern. The detailed syntax for regex patterns is defined by https://github.com/google/re2/wiki/Syntax. |
|
||||
| <span class="opa-keep-it-together">``output := regex.split(pattern, string)``</span> | ``output`` is ``array[string]`` representing elements of ``string`` separated by ``pattern`` |
|
||||
| <span class="opa-keep-it-together">``regex.globs_match(glob1, glob2)``</span> | true if the intersection of regex-style globs ``glob1`` and ``glob2`` matches a non-empty set of non-empty strings. The set of regex symbols is limited for this builtin: only ``.``, ``*``, ``+``, ``[``, ``-``, ``]`` and ``\`` are treated as special symbols. |
|
||||
| <span class="opa-keep-it-normal">``output := regex.template_match(pattern, string, delimiter_start, delimiter_end)``</span> | ``output`` is true if ``string`` matches ``pattern``. ``pattern`` is a string containing ``0..n`` regular expressions delimited by ``delimiter_start`` and ``delimiter_end``. Example ``regex.template_match("urn:foo:{.*}", "urn:foo:bar:baz", "{", "}")`` returns ``true``. |
|
||||
|
||||
+17
-1
@@ -9,7 +9,7 @@ import (
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/yashtewari/glob-intersection"
|
||||
gintersect "github.com/yashtewari/glob-intersection"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/topdown/builtins"
|
||||
@@ -18,6 +18,21 @@ import (
|
||||
var regexpCacheLock = sync.Mutex{}
|
||||
var regexpCache map[string]*regexp.Regexp
|
||||
|
||||
func builtinRegexIsValid(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
|
||||
s, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
|
||||
_, err = regexp.Compile(string(s))
|
||||
if err != nil {
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
|
||||
return iter(ast.BooleanTerm(true))
|
||||
}
|
||||
|
||||
func builtinRegexMatch(a, b ast.Value) (ast.Value, error) {
|
||||
s1, err := builtins.StringOperand(a, 1)
|
||||
if err != nil {
|
||||
@@ -192,6 +207,7 @@ func builtinRegexFindAllStringSubmatch(a, b, c ast.Value) (ast.Value, error) {
|
||||
|
||||
func init() {
|
||||
regexpCache = map[string]*regexp.Regexp{}
|
||||
RegisterBuiltinFunc(ast.RegexIsValid.Name, builtinRegexIsValid)
|
||||
RegisterFunctionalBuiltin2(ast.RegexMatch.Name, builtinRegexMatch)
|
||||
RegisterFunctionalBuiltin2(ast.RegexSplit.Name, builtinRegexSplit)
|
||||
RegisterFunctionalBuiltin2(ast.GlobsMatch.Name, builtinGlobsMatch)
|
||||
|
||||
+31
-1
@@ -1,6 +1,36 @@
|
||||
package topdown
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegexIsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "bad operand type",
|
||||
rules: []string{"p = x { regex.is_valid(data.num, x) }"},
|
||||
expected: "false",
|
||||
},
|
||||
{
|
||||
note: "bad pattern",
|
||||
rules: []string{"p = x { regex.is_valid(`++`, x) }"},
|
||||
expected: "false",
|
||||
},
|
||||
{
|
||||
note: "good pattern",
|
||||
rules: []string{"p = x { regex.is_valid(`.+`, x) }"},
|
||||
expected: "true",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{"num": json.Number("10")}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatchTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
||||
Reference in New Issue
Block a user