Support not in rules definition
This commit is contained in:
parent
b1fd3b8fc7
commit
dd04c432e9
4 changed files with 142 additions and 3 deletions
|
@ -256,6 +256,10 @@ The table below lists all the available matchers:
|
||||||
|
|
||||||
You can combine multiple matchers using the AND (`&&`) and OR (`||`) operators. You can also use parenthesis.
|
You can combine multiple matchers using the AND (`&&`) and OR (`||`) operators. You can also use parenthesis.
|
||||||
|
|
||||||
|
!!! info "Invert a matcher"
|
||||||
|
|
||||||
|
You can invert a matcher by using the `!` operator.
|
||||||
|
|
||||||
!!! important "Rule, Middleware, and Services"
|
!!! important "Rule, Middleware, and Services"
|
||||||
|
|
||||||
The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service.
|
The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service.
|
||||||
|
|
|
@ -7,6 +7,11 @@ import (
|
||||||
"github.com/vulcand/predicate"
|
"github.com/vulcand/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
and = "and"
|
||||||
|
or = "or"
|
||||||
|
)
|
||||||
|
|
||||||
type treeBuilder func() *tree
|
type treeBuilder func() *tree
|
||||||
|
|
||||||
// ParseDomains extract domains from rule.
|
// ParseDomains extract domains from rule.
|
||||||
|
@ -60,7 +65,7 @@ func lower(slice []string) []string {
|
||||||
|
|
||||||
func parseDomain(tree *tree) []string {
|
func parseDomain(tree *tree) []string {
|
||||||
switch tree.matcher {
|
switch tree.matcher {
|
||||||
case "and", "or":
|
case and, or:
|
||||||
return append(parseDomain(tree.ruleLeft), parseDomain(tree.ruleRight)...)
|
return append(parseDomain(tree.ruleLeft), parseDomain(tree.ruleRight)...)
|
||||||
case "Host", "HostSNI":
|
case "Host", "HostSNI":
|
||||||
return tree.value
|
return tree.value
|
||||||
|
@ -72,7 +77,7 @@ func parseDomain(tree *tree) []string {
|
||||||
func andFunc(left, right treeBuilder) treeBuilder {
|
func andFunc(left, right treeBuilder) treeBuilder {
|
||||||
return func() *tree {
|
return func() *tree {
|
||||||
return &tree{
|
return &tree{
|
||||||
matcher: "and",
|
matcher: and,
|
||||||
ruleLeft: left(),
|
ruleLeft: left(),
|
||||||
ruleRight: right(),
|
ruleRight: right(),
|
||||||
}
|
}
|
||||||
|
@ -82,13 +87,36 @@ func andFunc(left, right treeBuilder) treeBuilder {
|
||||||
func orFunc(left, right treeBuilder) treeBuilder {
|
func orFunc(left, right treeBuilder) treeBuilder {
|
||||||
return func() *tree {
|
return func() *tree {
|
||||||
return &tree{
|
return &tree{
|
||||||
matcher: "or",
|
matcher: or,
|
||||||
ruleLeft: left(),
|
ruleLeft: left(),
|
||||||
ruleRight: right(),
|
ruleRight: right(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invert(t *tree) *tree {
|
||||||
|
switch t.matcher {
|
||||||
|
case or:
|
||||||
|
t.matcher = and
|
||||||
|
t.ruleLeft = invert(t.ruleLeft)
|
||||||
|
t.ruleRight = invert(t.ruleRight)
|
||||||
|
case and:
|
||||||
|
t.matcher = or
|
||||||
|
t.ruleLeft = invert(t.ruleLeft)
|
||||||
|
t.ruleRight = invert(t.ruleRight)
|
||||||
|
default:
|
||||||
|
t.not = !t.not
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func notFunc(elem treeBuilder) treeBuilder {
|
||||||
|
return func() *tree {
|
||||||
|
return invert(elem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newParser() (predicate.Parser, error) {
|
func newParser() (predicate.Parser, error) {
|
||||||
parserFuncs := make(map[string]interface{})
|
parserFuncs := make(map[string]interface{})
|
||||||
|
|
||||||
|
@ -112,6 +140,7 @@ func newParser() (predicate.Parser, error) {
|
||||||
Operators: predicate.Operators{
|
Operators: predicate.Operators{
|
||||||
AND: andFunc,
|
AND: andFunc,
|
||||||
OR: orFunc,
|
OR: orFunc,
|
||||||
|
NOT: notFunc,
|
||||||
},
|
},
|
||||||
Functions: parserFuncs,
|
Functions: parserFuncs,
|
||||||
})
|
})
|
||||||
|
|
|
@ -72,6 +72,7 @@ func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error
|
||||||
|
|
||||||
type tree struct {
|
type tree struct {
|
||||||
matcher string
|
matcher string
|
||||||
|
not bool
|
||||||
value []string
|
value []string
|
||||||
ruleLeft *tree
|
ruleLeft *tree
|
||||||
ruleRight *tree
|
ruleRight *tree
|
||||||
|
@ -215,10 +216,27 @@ func addRuleOnRouter(router *mux.Router, rule *tree) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.not {
|
||||||
|
return not(funcs[rule.matcher])(router.NewRoute(), rule.value...)
|
||||||
|
}
|
||||||
return funcs[rule.matcher](router.NewRoute(), rule.value...)
|
return funcs[rule.matcher](router.NewRoute(), rule.value...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func not(m func(*mux.Route, ...string) error) func(*mux.Route, ...string) error {
|
||||||
|
return func(r *mux.Route, v ...string) error {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
err := m(router.NewRoute(), v...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.MatcherFunc(func(req *http.Request, ma *mux.RouteMatch) bool {
|
||||||
|
return !router.Match(req, ma)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addRuleOnRoute(route *mux.Route, rule *tree) error {
|
func addRuleOnRoute(route *mux.Route, rule *tree) error {
|
||||||
switch rule.matcher {
|
switch rule.matcher {
|
||||||
case "and":
|
case "and":
|
||||||
|
@ -243,6 +261,9 @@ func addRuleOnRoute(route *mux.Route, rule *tree) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.not {
|
||||||
|
return not(funcs[rule.matcher])(route, rule.value...)
|
||||||
|
}
|
||||||
return funcs[rule.matcher](route, rule.value...)
|
return funcs[rule.matcher](route, rule.value...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,10 +435,95 @@ func Test_addRoute(t *testing.T) {
|
||||||
rule: `Host("tchouk") && Path("", "/titi")`,
|
rule: `Host("tchouk") && Path("", "/titi")`,
|
||||||
expectedError: true,
|
expectedError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with not",
|
||||||
|
rule: `!Host("tchouk")`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://test/powpow": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with not on Path",
|
||||||
|
rule: `!Path("/titi")`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://tchouk/powpow": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with not on multiple route with or",
|
||||||
|
rule: `!(Host("tchouk") || Host("toto"))`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://toto/powpow": http.StatusNotFound,
|
||||||
|
"http://test/powpow": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with not on multiple route with and",
|
||||||
|
rule: `!(Host("tchouk") && Path("/titi"))`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://tchouk/toto": http.StatusOK,
|
||||||
|
"http://test/titi": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with not on multiple route with and and another not",
|
||||||
|
rule: `!(Host("tchouk") && !Path("/titi"))`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusOK,
|
||||||
|
"http://toto/titi": http.StatusOK,
|
||||||
|
"http://tchouk/toto": http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with not on two rule",
|
||||||
|
rule: `!Host("tchouk") || !Path("/titi")`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://tchouk/toto": http.StatusOK,
|
||||||
|
"http://test/titi": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule case with double not",
|
||||||
|
rule: `!(!(Host("tchouk") && Pathprefix("/titi")))`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusOK,
|
||||||
|
"http://tchouk/powpow": http.StatusNotFound,
|
||||||
|
"http://test/titi": http.StatusNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule case with not domain",
|
||||||
|
rule: `!Host("tchouk") && Pathprefix("/titi")`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://tchouk/powpow": http.StatusNotFound,
|
||||||
|
"http://toto/powpow": http.StatusNotFound,
|
||||||
|
"http://toto/titi": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Rule with multiple host AND multiple path AND not",
|
||||||
|
rule: `!(Host("tchouk","pouet") && Path("/powpow", "/titi"))`,
|
||||||
|
expected: map[string]int{
|
||||||
|
"http://tchouk/toto": http.StatusOK,
|
||||||
|
"http://tchouk/powpow": http.StatusNotFound,
|
||||||
|
"http://pouet/powpow": http.StatusNotFound,
|
||||||
|
"http://tchouk/titi": http.StatusNotFound,
|
||||||
|
"http://pouet/titi": http.StatusNotFound,
|
||||||
|
"http://pouet/toto": http.StatusOK,
|
||||||
|
"http://plopi/a": http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue