refactor(errs): migrate from gperr.Error to standard Go error interface

This is a large-scale refactoring across the codebase that replaces the custom
`gperr.Error` type with Go's standard `error` interface. The changes include:

- Replacing `gperr.Error` return types with `error` in function signatures
- Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()`
- Using `%w` format verb for error wrapping instead of `.With()` method
- Replacing `gperr.Subject()` calls with `gperr.PrependSubject()`
- Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern
- Update NewLogger to handle multiline error message
- Updating `goutils` submodule to latest commit

This refactoring aligns with Go idioms and removes the dependency on
custom error handling abstractions in favor of standard library patterns.
This commit is contained in:
yusing
2026-02-08 12:07:36 +08:00
parent 7eb2a78041
commit 6da7227f9b
118 changed files with 572 additions and 563 deletions

View File

@@ -72,7 +72,7 @@ var commands = map[string]struct {
description: makeLines("Require HTTP authentication for incoming requests"),
args: map[string]string{},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 0 {
return nil, ErrExpectNoArg
}
@@ -103,17 +103,17 @@ var commands = map[string]struct {
"to": "the path to rewrite to, must start with /",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 2 {
return nil, ErrExpectTwoArgs
}
path1, err1 := validateURLPath(args[:1])
path2, err2 := validateURLPath(args[1:])
if err1 != nil {
err1 = gperr.Errorf("from: %w", err1)
err1 = gperr.PrependSubject(err1, "from")
}
if err2 != nil {
err2 = gperr.Errorf("to: %w", err2)
err2 = gperr.PrependSubject(err2, "to")
}
if err1 != nil || err2 != nil {
return nil, gperr.Join(err1, err2)
@@ -189,7 +189,7 @@ var commands = map[string]struct {
"route": "the route to route to",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -227,7 +227,7 @@ var commands = map[string]struct {
"text": "the error message to return",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 2 {
return nil, ErrExpectTwoArgs
}
@@ -267,7 +267,7 @@ var commands = map[string]struct {
"realm": "the authentication realm",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) == 1 {
return args[0], nil
}
@@ -334,7 +334,7 @@ var commands = map[string]struct {
"value": "the value to set",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
return validateModField(ModFieldSet, args)
},
build: func(args any) CommandHandler {
@@ -354,7 +354,7 @@ var commands = map[string]struct {
"value": "the value to add",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
return validateModField(ModFieldAdd, args)
},
build: func(args any) CommandHandler {
@@ -373,7 +373,7 @@ var commands = map[string]struct {
"field": "the field to remove",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
return validateModField(ModFieldRemove, args)
},
build: func(args any) CommandHandler {
@@ -398,7 +398,7 @@ var commands = map[string]struct {
"template": "the template to log",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 3 {
return nil, ErrExpectThreeArgs
}
@@ -455,7 +455,7 @@ var commands = map[string]struct {
"body": "the body of the notification",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 4 {
return nil, ErrExpectFourArgs
}
@@ -543,7 +543,7 @@ func (cmd *Command) Parse(v string) error {
validArgs, err := builder.validate(args)
if err != nil {
// Only attach help for the directive that failed, avoid bringing in unrelated KV errors
return err.Subject(directive).With(builder.help.Error())
return gperr.PrependSubject(err, directive).With(builder.help.Error())
}
handler := builder.build(validArgs)

View File

@@ -12,7 +12,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
)
// mockUpstream creates a simple upstream handler for testing
@@ -32,7 +31,7 @@ func mockUpstreamWithHeaders(status int, body string, headers http.Header) http.
}
}
func parseRules(data string, target *Rules) gperr.Error {
func parseRules(data string, target *Rules) error {
_, err := serialization.ConvertString(data, reflect.ValueOf(target))
return err
}

View File

@@ -6,7 +6,6 @@ import (
"net/url"
"strconv"
gperr "github.com/yusing/goutils/errs"
httputils "github.com/yusing/goutils/http"
ioutils "github.com/yusing/goutils/io"
)
@@ -228,7 +227,7 @@ var modFields = map[string]struct {
"template": "the body template",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -273,7 +272,7 @@ var modFields = map[string]struct {
"template": "the response body template",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -301,7 +300,7 @@ var modFields = map[string]struct {
"code": "the status code",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}

View File

@@ -3,7 +3,7 @@ package rules
import (
"testing"
gperr "github.com/yusing/goutils/errs"
"github.com/rs/zerolog/log"
)
func TestErrorFormat(t *testing.T) {
@@ -19,5 +19,5 @@ func TestErrorFormat(t *testing.T) {
do: set invalid_command
- do: set resp_body "{{ .Request.Method {{ .Request.URL.Path }}"
`, &rules)
gperr.LogError("error", err)
log.Err(err).Msg("error")
}

View File

@@ -131,7 +131,7 @@ Generate help string as error, e.g.
from: the path to rewrite, must start with /
to: the path to rewrite to, must start with /
*/
func (h *Help) Error() gperr.Error {
func (h *Help) Error() error {
var lines gperr.MultilineError
lines.Adds(ansi.WithANSI(h.command, ansi.HighlightGreen))

View File

@@ -17,7 +17,6 @@ import (
"github.com/yusing/godoxy/internal/route"
"github.com/yusing/godoxy/internal/route/routes"
"github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
"golang.org/x/crypto/bcrypt"
. "github.com/yusing/godoxy/internal/route/rules"
@@ -44,7 +43,7 @@ func mockRoute(alias string) *route.FileServer {
return &route.FileServer{Route: &route.Route{Alias: alias}}
}
func parseRules(data string, target *Rules) gperr.Error {
func parseRules(data string, target *Rules) error {
_, err := serialization.ConvertString(strings.TrimSpace(data), reflect.ValueOf(target))
return err
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/logging/accesslog"
gperr "github.com/yusing/goutils/errs"
)
type noopWriteCloser struct {
@@ -31,7 +30,7 @@ var (
testFilesLock sync.Mutex
)
func openFile(path string) (io.WriteCloser, gperr.Error) {
func openFile(path string) (io.WriteCloser, error) {
switch path {
case "/dev/stdout":
return stdout, nil

View File

@@ -59,7 +59,7 @@ var checkers = map[string]struct {
),
args: map[string]string{},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 0 {
return nil, ErrExpectNoArg
}
@@ -251,7 +251,7 @@ var checkers = map[string]struct {
"proto": "the http protocol (http, https, h3)",
},
},
validate: func(args []string) (any, gperr.Error) {
validate: func(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -581,7 +581,7 @@ func (on *RuleOn) Parse(v string) error {
}
parsed, isResp, err := parseOn(rule)
if err != nil {
errs.Add(err.Subjectf("line %d", i+1))
errs.AddSubjectf(err, "line %d", i+1)
continue
}
if isResp {
@@ -603,7 +603,7 @@ func (on *RuleOn) MarshalText() ([]byte, error) {
return []byte(on.String()), nil
}
func parseOn(line string) (Checker, bool, gperr.Error) {
func parseOn(line string) (Checker, bool, error) {
ors := splitPipe(line)
if len(ors) > 1 {
@@ -645,7 +645,7 @@ func parseOn(line string) (Checker, bool, gperr.Error) {
validArgs, err := checker.validate(args)
if err != nil {
return nil, false, err.With(checker.help.Error())
return nil, false, gperr.Wrap(err).With(checker.help.Error())
}
checkFunc := checker.builder(validArgs)

View File

@@ -31,7 +31,7 @@ var quoteChars = [256]bool{
// error 403 "Forbidden 'foo' 'bar'"
// error 403 Forbidden\ \"foo\"\ \"bar\".
// error 403 "Message: ${CLOUDFLARE_API_KEY}"
func parse(v string) (subject string, args []string, err gperr.Error) {
func parse(v string) (subject string, args []string, err error) {
buf := bytes.NewBuffer(make([]byte, 0, len(v)))
escaped := false

View File

@@ -8,7 +8,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/route/rules"
"github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
)
//go:embed *.yml
@@ -35,12 +34,12 @@ func initPresets() {
var rules rules.Rules
content, err := fs.ReadFile(file.Name())
if err != nil {
gperr.LogError("failed to read rule preset", err)
log.Err(err).Msg("failed to read rule preset")
continue
}
_, err = serialization.ConvertString(string(content), reflect.ValueOf(&rules))
if err != nil {
gperr.LogError("failed to unmarshal rule preset", err)
log.Err(err).Msg("failed to unmarshal rule preset")
continue
}
rulePresets[file.Name()] = rules

View File

@@ -7,7 +7,6 @@ import (
"github.com/quic-go/quic-go/http3"
"github.com/rs/zerolog/log"
gperr "github.com/yusing/goutils/errs"
httputils "github.com/yusing/goutils/http"
"golang.org/x/net/http2"
@@ -58,7 +57,7 @@ func (rule *Rule) IsResponseRule() bool {
return rule.On.IsResponseChecker() || rule.Do.IsResponseHandler()
}
func (rules Rules) Validate() gperr.Error {
func (rules Rules) Validate() error {
var defaultRulesFound []int
for i, rule := range rules {
if rule.Name == "default" || rule.On.raw == OnDefault {

View File

@@ -16,7 +16,7 @@ import (
)
type (
ValidateFunc func(args []string) (any, gperr.Error)
ValidateFunc func(args []string) (any, error)
Tuple[T1, T2 any] struct {
First T1
Second T2
@@ -62,7 +62,7 @@ func (t *Tuple4[T1, T2, T3, T4]) String() string {
}
// validateSingleMatcher returns Matcher with the matcher validated.
func validateSingleMatcher(args []string) (any, gperr.Error) {
func validateSingleMatcher(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -70,7 +70,7 @@ func validateSingleMatcher(args []string) (any, gperr.Error) {
}
// toKVOptionalVMatcher returns *MapValueMatcher that value is optional.
func toKVOptionalVMatcher(args []string) (any, gperr.Error) {
func toKVOptionalVMatcher(args []string) (any, error) {
switch len(args) {
case 1:
return &MapValueMatcher{args[0], nil}, nil
@@ -85,7 +85,7 @@ func toKVOptionalVMatcher(args []string) (any, gperr.Error) {
}
}
func toKeyValueTemplate(args []string) (any, gperr.Error) {
func toKeyValueTemplate(args []string) (any, error) {
if len(args) != 2 {
return nil, ErrExpectTwoArgs
}
@@ -98,7 +98,7 @@ func toKeyValueTemplate(args []string) (any, gperr.Error) {
}
// validateURL returns types.URL with the URL validated.
func validateURL(args []string) (any, gperr.Error) {
func validateURL(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -116,7 +116,7 @@ func validateURL(args []string) (any, gperr.Error) {
}
// validateCIDR returns types.CIDR with the CIDR validated.
func validateCIDR(args []string) (any, gperr.Error) {
func validateCIDR(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -131,7 +131,7 @@ func validateCIDR(args []string) (any, gperr.Error) {
}
// validateURLPath returns string with the path validated.
func validateURLPath(args []string) (any, gperr.Error) {
func validateURLPath(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -148,7 +148,7 @@ func validateURLPath(args []string) (any, gperr.Error) {
return p, nil
}
func validateURLPathMatcher(args []string) (any, gperr.Error) {
func validateURLPathMatcher(args []string) (any, error) {
path, err := validateURLPath(args)
if err != nil {
return nil, err
@@ -157,7 +157,7 @@ func validateURLPathMatcher(args []string) (any, gperr.Error) {
}
// validateFSPath returns string with the path validated.
func validateFSPath(args []string) (any, gperr.Error) {
func validateFSPath(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -169,7 +169,7 @@ func validateFSPath(args []string) (any, gperr.Error) {
}
// validateMethod returns string with the method validated.
func validateMethod(args []string) (any, gperr.Error) {
func validateMethod(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -200,7 +200,7 @@ func validateStatusCode(status string) (int, error) {
// - 3xx
// - 4xx
// - 5xx
func validateStatusRange(args []string) (any, gperr.Error) {
func validateStatusRange(args []string) (any, error) {
if len(args) != 1 {
return nil, ErrExpectOneArg
}
@@ -232,7 +232,7 @@ func validateStatusRange(args []string) (any, gperr.Error) {
}
// validateUserBCryptPassword returns *HashedCrendential with the password validated.
func validateUserBCryptPassword(args []string) (any, gperr.Error) {
func validateUserBCryptPassword(args []string) (any, error) {
if len(args) != 2 {
return nil, ErrExpectTwoArgs
}
@@ -240,7 +240,7 @@ func validateUserBCryptPassword(args []string) (any, gperr.Error) {
}
// validateModField returns CommandHandler with the field validated.
func validateModField(mod FieldModifier, args []string) (CommandHandler, gperr.Error) {
func validateModField(mod FieldModifier, args []string) (CommandHandler, error) {
if len(args) == 0 {
return nil, ErrExpectTwoOrThreeArgs
}
@@ -257,7 +257,7 @@ func validateModField(mod FieldModifier, args []string) (CommandHandler, gperr.E
}
validArgs, err := setField.validate(args[1:])
if err != nil {
return nil, err.With(setField.help.Error())
return nil, gperr.Wrap(err).With(setField.help.Error())
}
modder := setField.builder(validArgs)
switch mod {
@@ -281,7 +281,7 @@ func validateModField(mod FieldModifier, args []string) (CommandHandler, gperr.E
return set, nil
}
func validateTemplate(tmplStr string, newline bool) (templateString, gperr.Error) {
func validateTemplate(tmplStr string, newline bool) (templateString, error) {
if newline && !strings.HasSuffix(tmplStr, "\n") {
tmplStr += "\n"
}
@@ -292,22 +292,15 @@ func validateTemplate(tmplStr string, newline bool) (templateString, gperr.Error
err := ValidateVars(tmplStr)
if err != nil {
return templateString{}, gperr.Wrap(err)
return templateString{}, err
}
return templateString{tmplStr, true}, nil
}
func validateLevel(level string) (zerolog.Level, gperr.Error) {
func validateLevel(level string) (zerolog.Level, error) {
l, err := zerolog.ParseLevel(level)
if err != nil {
return zerolog.NoLevel, ErrInvalidArguments.With(err)
}
return l, nil
}
// func validateNotifProvider(provider string) gperr.Error {
// if !notif.HasProvider(provider) {
// return ErrInvalidArguments.Subject(provider)
// }
// return nil
// }