mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 22:30:47 +01:00
feat(rules): add "on: default" rule syntax for default rule
- Add OnDefault rule type that matches when no other rules match - Add validation to prevent multiple default rules - Fix typo: extension → extensions in route config JSON tag
This commit is contained in:
@@ -55,7 +55,7 @@ type (
|
|||||||
|
|
||||||
route.HTTPConfig
|
route.HTTPConfig
|
||||||
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
||||||
Rules rules.Rules `json:"rules,omitempty" extension:"x-nullable"`
|
Rules rules.Rules `json:"rules,omitempty" extensions:"x-nullable"`
|
||||||
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
|
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
|
||||||
HealthCheck types.HealthCheckConfig `json:"healthcheck,omitempty" extensions:"x-nullable"` // null on load-balancer routes
|
HealthCheck types.HealthCheckConfig `json:"healthcheck,omitempty" extensions:"x-nullable"` // null on load-balancer routes
|
||||||
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ var (
|
|||||||
ErrInvalidArguments = gperr.New("invalid arguments")
|
ErrInvalidArguments = gperr.New("invalid arguments")
|
||||||
ErrInvalidOnTarget = gperr.New("invalid `rule.on` target")
|
ErrInvalidOnTarget = gperr.New("invalid `rule.on` target")
|
||||||
ErrInvalidCommandSequence = gperr.New("invalid command sequence")
|
ErrInvalidCommandSequence = gperr.New("invalid command sequence")
|
||||||
|
ErrMultipleDefaultRules = gperr.New("multiple default rules")
|
||||||
|
|
||||||
// vars errors
|
// vars errors
|
||||||
ErrNoArgProvided = gperr.New("no argument provided")
|
ErrNoArgProvided = gperr.New("no argument provided")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func (on *RuleOn) Check(w http.ResponseWriter, r *http.Request) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
OnDefault = "default"
|
||||||
OnHeader = "header"
|
OnHeader = "header"
|
||||||
OnQuery = "query"
|
OnQuery = "query"
|
||||||
OnCookie = "cookie"
|
OnCookie = "cookie"
|
||||||
@@ -50,6 +51,22 @@ var checkers = map[string]struct {
|
|||||||
builder func(args any) CheckFunc
|
builder func(args any) CheckFunc
|
||||||
isResponseChecker bool
|
isResponseChecker bool
|
||||||
}{
|
}{
|
||||||
|
OnDefault: {
|
||||||
|
help: Help{
|
||||||
|
command: OnDefault,
|
||||||
|
description: makeLines(
|
||||||
|
"The default rule is matched when no other rules are matched.",
|
||||||
|
),
|
||||||
|
args: map[string]string{},
|
||||||
|
},
|
||||||
|
validate: func(args []string) (any, gperr.Error) {
|
||||||
|
if len(args) != 0 {
|
||||||
|
return nil, ErrExpectNoArg
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
builder: func(args any) CheckFunc { return func(w http.ResponseWriter, r *http.Request) bool { return false } }, // this should never be called
|
||||||
|
},
|
||||||
OnHeader: {
|
OnHeader: {
|
||||||
help: Help{
|
help: Help{
|
||||||
command: OnHeader,
|
command: OnHeader,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
gperr "github.com/yusing/goutils/errs"
|
||||||
httputils "github.com/yusing/goutils/http"
|
httputils "github.com/yusing/goutils/http"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
@@ -57,6 +58,19 @@ func (rule *Rule) IsResponseRule() bool {
|
|||||||
return rule.On.IsResponseChecker() || rule.Do.IsResponseHandler()
|
return rule.On.IsResponseChecker() || rule.Do.IsResponseHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rules Rules) Validate() gperr.Error {
|
||||||
|
var defaultRulesFound []int
|
||||||
|
for i, rule := range rules {
|
||||||
|
if rule.Name == "default" || rule.On.raw == OnDefault {
|
||||||
|
defaultRulesFound = append(defaultRulesFound, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(defaultRulesFound) > 1 {
|
||||||
|
return ErrMultipleDefaultRules.Withf("found %d", len(defaultRulesFound))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// BuildHandler returns a http.HandlerFunc that implements the rules.
|
// BuildHandler returns a http.HandlerFunc that implements the rules.
|
||||||
func (rules Rules) BuildHandler(up http.HandlerFunc) http.HandlerFunc {
|
func (rules Rules) BuildHandler(up http.HandlerFunc) http.HandlerFunc {
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
@@ -74,7 +88,7 @@ func (rules Rules) BuildHandler(up http.HandlerFunc) http.HandlerFunc {
|
|||||||
var nonDefaultRules Rules
|
var nonDefaultRules Rules
|
||||||
hasDefaultRule := false
|
hasDefaultRule := false
|
||||||
for i, rule := range rules {
|
for i, rule := range rules {
|
||||||
if rule.Name == "default" {
|
if rule.Name == "default" || rule.On.raw == OnDefault {
|
||||||
defaultRule = rule
|
defaultRule = rule
|
||||||
hasDefaultRule = true
|
hasDefaultRule = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
52
internal/route/rules/rules_test.go
Normal file
52
internal/route/rules/rules_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/yusing/godoxy/internal/serialization"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRulesValidate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rules string
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no default rule",
|
||||||
|
rules: `
|
||||||
|
- name: rule1
|
||||||
|
on: header Host example.com
|
||||||
|
do: pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple default rules",
|
||||||
|
rules: `
|
||||||
|
- name: default
|
||||||
|
do: pass
|
||||||
|
- name: rule1
|
||||||
|
on: default
|
||||||
|
do: pass
|
||||||
|
`,
|
||||||
|
want: ErrMultipleDefaultRules,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var rules Rules
|
||||||
|
convertible, err := serialization.ConvertString(strings.TrimSpace(tt.rules), reflect.ValueOf(&rules))
|
||||||
|
require.True(t, convertible)
|
||||||
|
|
||||||
|
if tt.want == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.ErrorIs(t, err, tt.want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user