add help messages to rules, updat url validation

This commit is contained in:
yusing 2025-01-09 13:49:15 +08:00
parent 4aee44fe11
commit 9d701ad671
4 changed files with 126 additions and 8 deletions

View file

@ -36,10 +36,18 @@ const (
) )
var commands = map[string]struct { var commands = map[string]struct {
help Help
validate ValidateFunc validate ValidateFunc
build func(args any) *CommandExecutor build func(args any) *CommandExecutor
}{ }{
CommandRewrite: { CommandRewrite: {
help: Help{
command: CommandRewrite,
args: map[string]string{
"from": "the path to rewrite, must start with /",
"to": "the path to rewrite to, must start with /",
},
},
validate: func(args []string) (any, E.Error) { validate: func(args []string) (any, E.Error) {
if len(args) != 2 { if len(args) != 2 {
return nil, ErrExpectTwoArgs return nil, ErrExpectTwoArgs
@ -68,6 +76,12 @@ var commands = map[string]struct {
}, },
}, },
CommandServe: { CommandServe: {
help: Help{
command: CommandServe,
args: map[string]string{
"root": "the file system path to serve, must be an existing directory",
},
},
validate: validateFSPath, validate: validateFSPath,
build: func(args any) *CommandExecutor { build: func(args any) *CommandExecutor {
root := args.(string) root := args.(string)
@ -80,6 +94,12 @@ var commands = map[string]struct {
}, },
}, },
CommandRedirect: { CommandRedirect: {
help: Help{
command: CommandRedirect,
args: map[string]string{
"to": "the url to redirect to, can be relative or absolute URL",
},
},
validate: validateURL, validate: validateURL,
build: func(args any) *CommandExecutor { build: func(args any) *CommandExecutor {
target := args.(types.URL).String() target := args.(types.URL).String()
@ -92,6 +112,13 @@ var commands = map[string]struct {
}, },
}, },
CommandError: { CommandError: {
help: Help{
command: CommandError,
args: map[string]string{
"code": "the http status code to return",
"text": "the error message to return",
},
},
validate: func(args []string) (any, E.Error) { validate: func(args []string) (any, E.Error) {
if len(args) != 2 { if len(args) != 2 {
return nil, ErrExpectTwoArgs return nil, ErrExpectTwoArgs
@ -118,7 +145,13 @@ var commands = map[string]struct {
}, },
}, },
CommandProxy: { CommandProxy: {
validate: validateURL, help: Help{
command: CommandProxy,
args: map[string]string{
"to": "the url to proxy to, must be an absolute URL",
},
},
validate: validateAbsoluteURL,
build: func(args any) *CommandExecutor { build: func(args any) *CommandExecutor {
target := args.(types.URL) target := args.(types.URL)
if target.Scheme == "" { if target.Scheme == "" {
@ -166,7 +199,7 @@ func (cmd *Command) Parse(v string) error {
} }
validArgs, err := builder.validate(args) validArgs, err := builder.validate(args)
if err != nil { if err != nil {
return err.Subject(directive) return err.Subject(directive).Withf("%s", builder.help.String())
} }
exec := builder.build(validArgs) exec := builder.build(validArgs)

View file

@ -0,0 +1,35 @@
package rules
import "strings"
type Help struct {
command string
args map[string]string // args[arg] -> description
}
/*
Generate help string, e.g.
rewrite <from> <to>
from: the path to rewrite, must start with /
to: the path to rewrite to, must start with /
*/
func (h *Help) String() string {
var sb strings.Builder
sb.WriteString(h.command)
sb.WriteString(" ")
for arg := range h.args {
sb.WriteRune('<')
sb.WriteString(arg)
sb.WriteString("> ")
}
sb.WriteRune('\n')
for arg, desc := range h.args {
sb.WriteRune('\t')
sb.WriteString(arg)
sb.WriteString(": ")
sb.WriteString(desc)
sb.WriteRune('\n')
}
return sb.String()
}

View file

@ -26,28 +26,55 @@ const (
) )
var checkers = map[string]struct { var checkers = map[string]struct {
help Help
validate ValidateFunc validate ValidateFunc
check func(r *http.Request, args any) bool check func(r *http.Request, args any) bool
}{ }{
OnHeader: { // header <key> <value> OnHeader: {
help: Help{
command: OnHeader,
args: map[string]string{
"key": "the header key",
"value": "the header value",
},
},
validate: toStrTuple, validate: toStrTuple,
check: func(r *http.Request, args any) bool { check: func(r *http.Request, args any) bool {
return r.Header.Get(args.(StrTuple).First) == args.(StrTuple).Second return r.Header.Get(args.(StrTuple).First) == args.(StrTuple).Second
}, },
}, },
OnQuery: { // query <key> <value> OnQuery: {
help: Help{
command: OnQuery,
args: map[string]string{
"key": "the query key",
"value": "the query value",
},
},
validate: toStrTuple, validate: toStrTuple,
check: func(r *http.Request, args any) bool { check: func(r *http.Request, args any) bool {
return r.URL.Query().Get(args.(StrTuple).First) == args.(StrTuple).Second return r.URL.Query().Get(args.(StrTuple).First) == args.(StrTuple).Second
}, },
}, },
OnMethod: { // method <method> OnMethod: {
help: Help{
command: OnMethod,
args: map[string]string{
"method": "the http method",
},
},
validate: validateMethod, validate: validateMethod,
check: func(r *http.Request, method any) bool { check: func(r *http.Request, method any) bool {
return r.Method == method.(string) return r.Method == method.(string)
}, },
}, },
OnPath: { // path <path> OnPath: {
help: Help{
command: OnPath,
args: map[string]string{
"path": "the request path, must start with /",
},
},
validate: validateURLPath, validate: validateURLPath,
check: func(r *http.Request, globPath any) bool { check: func(r *http.Request, globPath any) bool {
reqPath := r.URL.Path reqPath := r.URL.Path
@ -57,7 +84,13 @@ var checkers = map[string]struct {
return strutils.GlobMatch(globPath.(string), reqPath) return strutils.GlobMatch(globPath.(string), reqPath)
}, },
}, },
OnRemote: { // remote <ip|cidr> OnRemote: {
help: Help{
command: OnRemote,
args: map[string]string{
"ip|cidr": "the remote ip or cidr",
},
},
validate: validateCIDR, validate: validateCIDR,
check: func(r *http.Request, cidr any) bool { check: func(r *http.Request, cidr any) bool {
host, _, err := net.SplitHostPort(r.RemoteAddr) host, _, err := net.SplitHostPort(r.RemoteAddr)
@ -137,7 +170,7 @@ func parseOn(line string) (Checkers, E.Error) {
validArgs, err := checker.validate(args) validArgs, err := checker.validate(args)
if err != nil { if err != nil {
return nil, err.Subject(subject) return nil, err.Subject(subject).Withf("%s", checker.help.String())
} }
return Checkers{ return Checkers{

View file

@ -35,6 +35,23 @@ func validateURL(args []string) (any, E.Error) {
return u, nil return u, nil
} }
func validateAbsoluteURL(args []string) (any, E.Error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
u, err := types.ParseURL(args[0])
if err != nil {
return nil, ErrInvalidArguments.With(err)
}
if u.Scheme == "" {
u.Scheme = "http"
}
if u.Host == "" {
return nil, ErrInvalidArguments.Withf("missing host")
}
return u, nil
}
func validateCIDR(args []string) (any, E.Error) { func validateCIDR(args []string) (any, E.Error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrExpectOneArg return nil, ErrExpectOneArg