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.
|
||||
|
||||
!!! info "Invert a matcher"
|
||||
|
||||
You can invert a matcher by using the `!` operator.
|
||||
|
||||
!!! 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.
|
||||
|
|
|
@ -7,6 +7,11 @@ import (
|
|||
"github.com/vulcand/predicate"
|
||||
)
|
||||
|
||||
const (
|
||||
and = "and"
|
||||
or = "or"
|
||||
)
|
||||
|
||||
type treeBuilder func() *tree
|
||||
|
||||
// ParseDomains extract domains from rule.
|
||||
|
@ -60,7 +65,7 @@ func lower(slice []string) []string {
|
|||
|
||||
func parseDomain(tree *tree) []string {
|
||||
switch tree.matcher {
|
||||
case "and", "or":
|
||||
case and, or:
|
||||
return append(parseDomain(tree.ruleLeft), parseDomain(tree.ruleRight)...)
|
||||
case "Host", "HostSNI":
|
||||
return tree.value
|
||||
|
@ -72,7 +77,7 @@ func parseDomain(tree *tree) []string {
|
|||
func andFunc(left, right treeBuilder) treeBuilder {
|
||||
return func() *tree {
|
||||
return &tree{
|
||||
matcher: "and",
|
||||
matcher: and,
|
||||
ruleLeft: left(),
|
||||
ruleRight: right(),
|
||||
}
|
||||
|
@ -82,13 +87,36 @@ func andFunc(left, right treeBuilder) treeBuilder {
|
|||
func orFunc(left, right treeBuilder) treeBuilder {
|
||||
return func() *tree {
|
||||
return &tree{
|
||||
matcher: "or",
|
||||
matcher: or,
|
||||
ruleLeft: left(),
|
||||
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) {
|
||||
parserFuncs := make(map[string]interface{})
|
||||
|
||||
|
@ -112,6 +140,7 @@ func newParser() (predicate.Parser, error) {
|
|||
Operators: predicate.Operators{
|
||||
AND: andFunc,
|
||||
OR: orFunc,
|
||||
NOT: notFunc,
|
||||
},
|
||||
Functions: parserFuncs,
|
||||
})
|
||||
|
|
|
@ -72,6 +72,7 @@ func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error
|
|||
|
||||
type tree struct {
|
||||
matcher string
|
||||
not bool
|
||||
value []string
|
||||
ruleLeft *tree
|
||||
ruleRight *tree
|
||||
|
@ -215,10 +216,27 @@ func addRuleOnRouter(router *mux.Router, rule *tree) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if rule.not {
|
||||
return not(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 {
|
||||
switch rule.matcher {
|
||||
case "and":
|
||||
|
@ -243,6 +261,9 @@ func addRuleOnRoute(route *mux.Route, rule *tree) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if rule.not {
|
||||
return not(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")`,
|
||||
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 {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
Loading…
Reference in a new issue