From 9d701ad671a5382fd01a146ae8517d0ff9e42f6c Mon Sep 17 00:00:00 2001 From: yusing Date: Thu, 9 Jan 2025 13:49:15 +0800 Subject: [PATCH] add help messages to rules, updat url validation --- internal/route/rules/do.go | 37 ++++++++++++++++++++++++-- internal/route/rules/help.go | 35 +++++++++++++++++++++++++ internal/route/rules/on.go | 45 +++++++++++++++++++++++++++----- internal/route/rules/validate.go | 17 ++++++++++++ 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 internal/route/rules/help.go diff --git a/internal/route/rules/do.go b/internal/route/rules/do.go index 45968be..819e27b 100644 --- a/internal/route/rules/do.go +++ b/internal/route/rules/do.go @@ -36,10 +36,18 @@ const ( ) var commands = map[string]struct { + help Help validate ValidateFunc build func(args any) *CommandExecutor }{ 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) { if len(args) != 2 { return nil, ErrExpectTwoArgs @@ -68,6 +76,12 @@ var commands = map[string]struct { }, }, CommandServe: { + help: Help{ + command: CommandServe, + args: map[string]string{ + "root": "the file system path to serve, must be an existing directory", + }, + }, validate: validateFSPath, build: func(args any) *CommandExecutor { root := args.(string) @@ -80,6 +94,12 @@ var commands = map[string]struct { }, }, CommandRedirect: { + help: Help{ + command: CommandRedirect, + args: map[string]string{ + "to": "the url to redirect to, can be relative or absolute URL", + }, + }, validate: validateURL, build: func(args any) *CommandExecutor { target := args.(types.URL).String() @@ -92,6 +112,13 @@ var commands = map[string]struct { }, }, 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) { if len(args) != 2 { return nil, ErrExpectTwoArgs @@ -118,7 +145,13 @@ var commands = map[string]struct { }, }, 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 { target := args.(types.URL) if target.Scheme == "" { @@ -166,7 +199,7 @@ func (cmd *Command) Parse(v string) error { } validArgs, err := builder.validate(args) if err != nil { - return err.Subject(directive) + return err.Subject(directive).Withf("%s", builder.help.String()) } exec := builder.build(validArgs) diff --git a/internal/route/rules/help.go b/internal/route/rules/help.go new file mode 100644 index 0000000..9f2b559 --- /dev/null +++ b/internal/route/rules/help.go @@ -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: 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() +} diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 7771366..326513d 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -26,28 +26,55 @@ const ( ) var checkers = map[string]struct { + help Help validate ValidateFunc check func(r *http.Request, args any) bool }{ - OnHeader: { // header + OnHeader: { + help: Help{ + command: OnHeader, + args: map[string]string{ + "key": "the header key", + "value": "the header value", + }, + }, validate: toStrTuple, check: func(r *http.Request, args any) bool { return r.Header.Get(args.(StrTuple).First) == args.(StrTuple).Second }, }, - OnQuery: { // query + OnQuery: { + help: Help{ + command: OnQuery, + args: map[string]string{ + "key": "the query key", + "value": "the query value", + }, + }, validate: toStrTuple, check: func(r *http.Request, args any) bool { return r.URL.Query().Get(args.(StrTuple).First) == args.(StrTuple).Second }, }, - OnMethod: { // method + OnMethod: { + help: Help{ + command: OnMethod, + args: map[string]string{ + "method": "the http method", + }, + }, validate: validateMethod, check: func(r *http.Request, method any) bool { return r.Method == method.(string) }, }, - OnPath: { // path + OnPath: { + help: Help{ + command: OnPath, + args: map[string]string{ + "path": "the request path, must start with /", + }, + }, validate: validateURLPath, check: func(r *http.Request, globPath any) bool { reqPath := r.URL.Path @@ -57,7 +84,13 @@ var checkers = map[string]struct { return strutils.GlobMatch(globPath.(string), reqPath) }, }, - OnRemote: { // remote + OnRemote: { + help: Help{ + command: OnRemote, + args: map[string]string{ + "ip|cidr": "the remote ip or cidr", + }, + }, validate: validateCIDR, check: func(r *http.Request, cidr any) bool { host, _, err := net.SplitHostPort(r.RemoteAddr) @@ -137,7 +170,7 @@ func parseOn(line string) (Checkers, E.Error) { validArgs, err := checker.validate(args) if err != nil { - return nil, err.Subject(subject) + return nil, err.Subject(subject).Withf("%s", checker.help.String()) } return Checkers{ diff --git a/internal/route/rules/validate.go b/internal/route/rules/validate.go index e6478b4..55621fb 100644 --- a/internal/route/rules/validate.go +++ b/internal/route/rules/validate.go @@ -35,6 +35,23 @@ func validateURL(args []string) (any, E.Error) { 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) { if len(args) != 1 { return nil, ErrExpectOneArg