diff --git a/agent/cmd/main.go b/agent/cmd/main.go index b948b301..684de430 100644 --- a/agent/cmd/main.go +++ b/agent/cmd/main.go @@ -19,7 +19,6 @@ import ( "github.com/yusing/godoxy/agent/pkg/handler" "github.com/yusing/godoxy/internal/metrics/systeminfo" socketproxy "github.com/yusing/godoxy/socketproxy/pkg" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" "github.com/yusing/goutils/task" "github.com/yusing/goutils/version" @@ -72,7 +71,7 @@ Tips: // - Otherwise: route to HTTPS API handler tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: env.AgentPort}) if err != nil { - gperr.LogFatal("failed to listen on port", err) + log.Fatal().Err(err).Msg("failed to listen on port") } caCertPool := x509.NewCertPool() @@ -148,7 +147,7 @@ Tips: log.Info().Msgf("%s socket listening on: %s", runtime, socketproxy.ListenAddr) l, err := net.Listen("tcp", socketproxy.ListenAddr) if err != nil { - gperr.LogFatal("failed to listen on port", err) + log.Fatal().Err(err).Msg("failed to listen on port") } errLog := log.Logger.With().Str("level", "error").Str("component", "socketproxy").Logger() srv := http.Server{ diff --git a/agent/pkg/agent/config.go b/agent/pkg/agent/config.go index 404554e4..300cbdd6 100644 --- a/agent/pkg/agent/config.go +++ b/agent/pkg/agent/config.go @@ -216,7 +216,7 @@ func (cfg *AgentConfig) InitWithCerts(ctx context.Context, ca, crt, key []byte) cfg.l = log.With().Str("agent", cfg.Name).Logger() if err := streamUnsupportedErrs.Error(); err != nil { - gperr.LogWarn("agent has limited/no stream tunneling support, TCP and UDP routes via agent will not work", err, &cfg.l) + cfg.l.Warn().Err(err).Msg("agent has limited/no stream tunneling support, TCP and UDP routes via agent will not work") } if serverVersion.IsNewerThanMajor(cfg.Version) { diff --git a/cmd/main.go b/cmd/main.go index 3494bdc3..14cccd40 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,7 +16,6 @@ import ( "github.com/yusing/godoxy/internal/logging/memlogger" "github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/route/rules" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" "github.com/yusing/goutils/version" ) @@ -64,9 +63,9 @@ func main() { if err != nil { var criticalErr config.CriticalError if errors.As(err, &criticalErr) { - gperr.LogFatal("critical error in config", criticalErr) + log.Fatal().Err(criticalErr).Msg("critical error in config") } - gperr.LogWarn("errors in config", err) + log.Warn().Err(err).Msg("errors in config") } if err := auth.Initialize(); err != nil { diff --git a/goutils b/goutils index 56663372..0f8a005f 160000 --- a/goutils +++ b/goutils @@ -1 +1 @@ -Subproject commit 56663372deda9b71ddfdc028840349fdfe06a6f9 +Subproject commit 0f8a005f8a574beeb35bb6303c4a7398aaa37ea8 diff --git a/internal/acl/README.md b/internal/acl/README.md index 91426367..b01c2722 100644 --- a/internal/acl/README.md +++ b/internal/acl/README.md @@ -54,13 +54,13 @@ type Matchers []Matcher ### Exported functions and methods ```go -func (c *Config) Validate() gperr.Error +func (c *Config) Validate() error ``` Validates configuration and sets defaults. Must be called before `Start`. ```go -func (c *Config) Start(parent task.Parent) gperr.Error +func (c *Config) Start(parent task.Parent) error ``` Initializes the ACL, starts the logger and notification goroutines. @@ -169,14 +169,14 @@ Configuration is loaded from `config/config.yml` under the `acl` key. ```yaml acl: - default: "allow" # "allow" or "deny" - allow_local: true # Allow private/loopback IPs + default: "allow" # "allow" or "deny" + allow_local: true # Allow private/loopback IPs log: - log_allowed: false # Log allowed connections + log_allowed: false # Log allowed connections notify: - to: ["gotify"] # Notification providers - interval: "1m" # Notification interval - include_allowed: false # Include allowed in notifications + to: ["gotify"] # Notification providers + interval: "1m" # Notification interval + include_allowed: false # Include allowed in notifications ``` ### Hot-reloading diff --git a/internal/acl/config.go b/internal/acl/config.go index bca4fc8a..534dbfa6 100644 --- a/internal/acl/config.go +++ b/internal/acl/config.go @@ -87,7 +87,7 @@ const ( ACLDeny = "deny" ) -func (c *Config) Validate() gperr.Error { +func (c *Config) Validate() error { switch c.Default { case "", ACLAllow: c.defaultAllow = true @@ -131,7 +131,7 @@ func (c *Config) Valid() bool { return c != nil && c.valErr == nil } -func (c *Config) Start(parent task.Parent) gperr.Error { +func (c *Config) Start(parent task.Parent) error { if c.Log != nil { logger, err := accesslog.NewAccessLogger(parent, c.Log) if err != nil { diff --git a/internal/acl/matcher.go b/internal/acl/matcher.go index e5cc3ae2..07f30c46 100644 --- a/internal/acl/matcher.go +++ b/internal/acl/matcher.go @@ -2,6 +2,7 @@ package acl import ( "bytes" + "errors" "net" "strings" @@ -38,9 +39,9 @@ var errMatcherFormat = gperr.Multiline().AddLines( ) var ( - errSyntax = gperr.New("syntax error") - errInvalidIP = gperr.New("invalid IP") - errInvalidCIDR = gperr.New("invalid CIDR") + errSyntax = errors.New("syntax error") + errInvalidIP = errors.New("invalid IP") + errInvalidCIDR = errors.New("invalid CIDR") ) func (matcher *Matcher) Parse(s string) error { diff --git a/internal/api/handler.go b/internal/api/handler.go index e4753538..e36eecab 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -21,7 +21,6 @@ import ( "github.com/yusing/godoxy/internal/auth" "github.com/yusing/godoxy/internal/common" apitypes "github.com/yusing/goutils/apitypes" - gperr "github.com/yusing/goutils/errs" ) // @title GoDoxy API @@ -203,9 +202,8 @@ func ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() if len(c.Errors) > 0 { - logger := log.With().Str("uri", c.Request.RequestURI).Logger() for _, err := range c.Errors { - gperr.LogError("Internal error", err.Err, &logger) + log.Err(err.Err).Str("uri", c.Request.RequestURI).Msg("Internal error") } if !c.IsWebsocket() { c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error")) diff --git a/internal/api/v1/agent/verify.go b/internal/api/v1/agent/verify.go index 7dbcc2d2..3dd42b67 100644 --- a/internal/api/v1/agent/verify.go +++ b/internal/api/v1/agent/verify.go @@ -2,6 +2,7 @@ package agentapi import ( "context" + "errors" "fmt" "net/http" "os" @@ -13,7 +14,6 @@ import ( config "github.com/yusing/godoxy/internal/config/types" "github.com/yusing/godoxy/internal/route/provider" apitypes "github.com/yusing/goutils/apitypes" - gperr "github.com/yusing/goutils/errs" ) type VerifyNewAgentRequest struct { @@ -84,9 +84,9 @@ func Verify(c *gin.Context) { c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded))) } -var errAgentAlreadyExists = gperr.New("agent already exists") +var errAgentAlreadyExists = errors.New("agent already exists") -func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) { +func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, error) { var agentCfg agent.AgentConfig agentCfg.Addr = host agentCfg.Runtime = containerRuntime @@ -105,12 +105,12 @@ func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client a err := agentCfg.InitWithCerts(ctx, ca.Cert, client.Cert, client.Key) if err != nil { - return 0, gperr.Wrap(err, "failed to initialize agent config") + return 0, fmt.Errorf("failed to initialize agent config: %w", err) } provider := provider.NewAgentProvider(&agentCfg) if _, loaded := cfgState.LoadOrStoreProvider(provider.String(), provider); loaded { - return 0, gperr.Errorf("provider %s already exists", provider.String()) + return 0, fmt.Errorf("provider %s already exists", provider.String()) } // agent must be added before loading routes @@ -122,7 +122,7 @@ func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client a if err != nil { cfgState.DeleteProvider(provider.String()) agentpool.Remove(&agentCfg) - return 0, gperr.Wrap(err, "failed to load routes") + return 0, fmt.Errorf("failed to load routes: %w", err) } return provider.NumRoutes(), nil diff --git a/internal/api/v1/docker/containers.go b/internal/api/v1/docker/containers.go index a07c24eb..78260d9d 100644 --- a/internal/api/v1/docker/containers.go +++ b/internal/api/v1/docker/containers.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" + "github.com/rs/zerolog/log" gperr "github.com/yusing/goutils/errs" _ "github.com/yusing/goutils/apitypes" @@ -36,18 +37,18 @@ func Containers(c *gin.Context) { serveHTTP[Container](c, GetContainers) } -func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Container, gperr.Error) { +func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Container, error) { errs := gperr.NewBuilder("failed to get containers") containers := make([]Container, 0) - for server, dockerClient := range dockerClients { + for name, dockerClient := range dockerClients { conts, err := dockerClient.ContainerList(ctx, client.ContainerListOptions{All: true}) if err != nil { - errs.Add(err) + errs.AddSubject(err, name) continue } for _, cont := range conts.Items { containers = append(containers, Container{ - Server: server, + Server: name, Name: cont.Names[0], ID: cont.ID, Image: cont.Image, @@ -59,11 +60,10 @@ func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Containe return containers[i].Name < containers[j].Name }) if err := errs.Error(); err != nil { - gperr.LogError("failed to get containers", err) - if len(containers) == 0 { - return nil, err + if len(containers) > 0 { + log.Err(err).Msg("failed to get containers from some servers") + return containers, nil } - return containers, nil } - return containers, nil + return containers, errs.Error() } diff --git a/internal/api/v1/docker/info.go b/internal/api/v1/docker/info.go index b96fc1d3..2c415a3d 100644 --- a/internal/api/v1/docker/info.go +++ b/internal/api/v1/docker/info.go @@ -59,7 +59,7 @@ func Info(c *gin.Context) { serveHTTP[dockerInfo](c, GetDockerInfo) } -func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerInfo, gperr.Error) { +func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerInfo, error) { errs := gperr.NewBuilder("failed to get docker info") dockerInfos := make([]dockerInfo, len(dockerClients)) @@ -67,7 +67,7 @@ func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerIn for name, dockerClient := range dockerClients { info, err := dockerClient.Info(ctx, client.InfoOptions{}) if err != nil { - errs.Add(err) + errs.AddSubject(err, name) continue } info.Info.Name = name diff --git a/internal/api/v1/docker/utils.go b/internal/api/v1/docker/utils.go index 328d19e4..657848d9 100644 --- a/internal/api/v1/docker/utils.go +++ b/internal/api/v1/docker/utils.go @@ -8,7 +8,6 @@ import ( "github.com/gin-gonic/gin" "github.com/yusing/godoxy/internal/docker" apitypes "github.com/yusing/goutils/apitypes" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/http/httpheaders" "github.com/yusing/goutils/http/websocket" ) @@ -39,7 +38,7 @@ func handleResult[V any, T ResultType[V]](c *gin.Context, errs error, result T) c.JSON(http.StatusOK, result) } -func serveHTTP[V any, T ResultType[V]](c *gin.Context, getResult func(ctx context.Context, dockerClients DockerClients) (T, gperr.Error)) { +func serveHTTP[V any, T ResultType[V]](c *gin.Context, getResult func(ctx context.Context, dockerClients DockerClients) (T, error)) { dockerClients := docker.Clients() defer closeAllClients(dockerClients) diff --git a/internal/api/v1/file/validate.go b/internal/api/v1/file/validate.go index e2fa07dc..62a78822 100644 --- a/internal/api/v1/file/validate.go +++ b/internal/api/v1/file/validate.go @@ -51,7 +51,7 @@ func Validate(c *gin.Context) { c.JSON(http.StatusOK, apitypes.Success("file validated")) } -func validateFile(fileType FileType, content []byte) gperr.Error { +func validateFile(fileType FileType, content []byte) error { switch fileType { case FileTypeConfig: return config.Validate(content) diff --git a/internal/api/v1/metrics/all_system_info.go b/internal/api/v1/metrics/all_system_info.go index fee4fc56..1449a341 100644 --- a/internal/api/v1/metrics/all_system_info.go +++ b/internal/api/v1/metrics/all_system_info.go @@ -113,7 +113,7 @@ func AllSystemInfo(c *gin.Context) { data, err := systeminfo.Poller.GetRespData(req.Period, query) if err != nil { numErrs.Add(1) - return gperr.PrependSubject("Main server", err) + return gperr.PrependSubject(err, "Main server") } select { case <-manager.Done(): @@ -133,7 +133,7 @@ func AllSystemInfo(c *gin.Context) { data, err := getAgentSystemInfoWithRetry(manager.Context(), a, queryEncoded) if err != nil { numErrs.Add(1) - return gperr.PrependSubject("Agent "+a.Name, err) + return gperr.PrependSubject(err, "Agent "+a.Name) } select { case <-manager.Done(): @@ -170,7 +170,7 @@ func AllSystemInfo(c *gin.Context) { c.Error(apitypes.InternalServerError(err, "failed to get all system info")) return } - gperr.LogWarn("failed to get some system info", err) + log.Warn().Err(err).Msg("failed to get some system info") } } } diff --git a/internal/api/v1/route/playground.go b/internal/api/v1/route/playground.go index b7321839..e4f464ed 100644 --- a/internal/api/v1/route/playground.go +++ b/internal/api/v1/route/playground.go @@ -1,6 +1,7 @@ package routeApi import ( + "fmt" "io" "net/http" "net/http/httptest" @@ -54,16 +55,16 @@ type PlaygroundResponse struct { MatchedRules []string `json:"matchedRules"` FinalRequest FinalRequest `json:"finalRequest"` FinalResponse FinalResponse `json:"finalResponse"` - ExecutionError gperr.Error `json:"executionError,omitempty"` + ExecutionError error `json:"executionError,omitempty"` // we need the structured error, not the plain string UpstreamCalled bool `json:"upstreamCalled"` } // @name PlaygroundResponse type ParsedRule struct { - Name string `json:"name"` - On string `json:"on"` - Do string `json:"do"` - ValidationError gperr.Error `json:"validationError,omitempty"` - IsResponseRule bool `json:"isResponseRule"` + Name string `json:"name"` + On string `json:"on"` + Do string `json:"do"` + ValidationError error `json:"validationError,omitempty"` // we need the structured error, not the plain string + IsResponseRule bool `json:"isResponseRule"` } // @name ParsedRule type FinalRequest struct { @@ -138,7 +139,7 @@ func Playground(c *gin.Context) { // Execute rules matchedRules := []string{} upstreamCalled := false - var executionError gperr.Error + var executionError error // Variables to capture modified request state var finalReqMethod, finalReqPath, finalReqHost string @@ -244,21 +245,23 @@ func Playground(c *gin.Context) { c.JSON(http.StatusOK, response) } -func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *gperr.Error) { +func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *error) { defer func() { if r := recover(); r != nil { if outErr != nil { - *outErr = gperr.Errorf("panic during rule execution: %v", r) + *outErr = fmt.Errorf("panic during rule execution: %v", r) } } }() h(w, r) } -func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) { +func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) { var parsedRules []ParsedRule var rulesList rules.Rules + var valErrs gperr.Builder + // Parse each rule individually to capture per-rule errors for _, rawRule := range rawRules { var rule rules.Rule @@ -284,7 +287,11 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) { // Determine if valid isValid := onErr == nil && doErr == nil - validationErr := gperr.Join(gperr.PrependSubject("on", onErr), gperr.PrependSubject("do", doErr)) + var validationErr error + if !isValid { + validationErr = gperr.Join(gperr.PrependSubject(onErr, "on"), gperr.PrependSubject(doErr, "do")) + valErrs.Add(validationErr) + } parsedRules = append(parsedRules, ParsedRule{ Name: name, @@ -300,7 +307,7 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) { } } - return parsedRules, rulesList, nil + return parsedRules, rulesList, valErrs.Error() } func createMockRequest(mock MockRequest) *http.Request { diff --git a/internal/auth/oidc.go b/internal/auth/oidc.go index 27fc732e..f41ec606 100644 --- a/internal/auth/oidc.go +++ b/internal/auth/oidc.go @@ -17,7 +17,6 @@ import ( "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/utils" - gperr "github.com/yusing/goutils/errs" httputils "github.com/yusing/goutils/http" "golang.org/x/oauth2" "golang.org/x/time/rate" @@ -76,8 +75,8 @@ const ( var ( errMissingIDToken = errors.New("missing id_token field from oauth token") - ErrMissingOAuthToken = gperr.New("missing oauth token") - ErrInvalidOAuthToken = gperr.New("invalid oauth token") + ErrMissingOAuthToken = errors.New("missing oauth token") + ErrInvalidOAuthToken = errors.New("invalid oauth token") ) // generateState generates a random string for OIDC state. diff --git a/internal/auth/userpass.go b/internal/auth/userpass.go index f68987eb..73bdb199 100644 --- a/internal/auth/userpass.go +++ b/internal/auth/userpass.go @@ -1,6 +1,7 @@ package auth import ( + "errors" "fmt" "net/http" "time" @@ -8,16 +9,12 @@ import ( "github.com/bytedance/sonic" "github.com/golang-jwt/jwt/v5" "github.com/yusing/godoxy/internal/common" - gperr "github.com/yusing/goutils/errs" httputils "github.com/yusing/goutils/http" strutils "github.com/yusing/goutils/strings" "golang.org/x/crypto/bcrypt" ) -var ( - ErrInvalidUsername = gperr.New("invalid username") - ErrInvalidPassword = gperr.New("invalid password") -) +var ErrInvalidUsername = errors.New("invalid username") type ( UserPassAuth struct { @@ -94,9 +91,9 @@ func (auth *UserPassAuth) CheckToken(r *http.Request) error { case !token.Valid: return ErrInvalidSessionToken case claims.Username != auth.username: - return ErrUserNotAllowed.Subject(claims.Username) + return fmt.Errorf("%w: %s", ErrUserNotAllowed, claims.Username) case claims.ExpiresAt.Before(time.Now()): - return gperr.Errorf("token expired on %s", strutils.FormatTime(claims.ExpiresAt.Time)) + return fmt.Errorf("token expired on %s", strutils.FormatTime(claims.ExpiresAt.Time)) } return nil @@ -140,10 +137,10 @@ func (auth *UserPassAuth) LogoutHandler(w http.ResponseWriter, r *http.Request) func (auth *UserPassAuth) validatePassword(user, pass string) error { if user != auth.username { - return ErrInvalidUsername.Subject(user) + return ErrInvalidUsername } if err := bcrypt.CompareHashAndPassword(auth.pwdHash, []byte(pass)); err != nil { - return ErrInvalidPassword.With(err).Subject(pass) + return err } return nil } diff --git a/internal/auth/userpass_test.go b/internal/auth/userpass_test.go index 75bdb7d0..ffbeb774 100644 --- a/internal/auth/userpass_test.go +++ b/internal/auth/userpass_test.go @@ -27,7 +27,7 @@ func TestUserPassValidateCredentials(t *testing.T) { err := auth.validatePassword("username", "password") expect.NoError(t, err) err = auth.validatePassword("username", "wrong-password") - expect.ErrorIs(t, ErrInvalidPassword, err) + expect.ErrorIs(t, bcrypt.ErrMismatchedHashAndPassword, err) err = auth.validatePassword("wrong-username", "password") expect.ErrorIs(t, ErrInvalidUsername, err) } diff --git a/internal/auth/utils.go b/internal/auth/utils.go index f674b286..ae90e6df 100644 --- a/internal/auth/utils.go +++ b/internal/auth/utils.go @@ -1,20 +1,20 @@ package auth import ( + "errors" "net" "net/http" "strings" "time" "github.com/yusing/godoxy/internal/common" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" ) var ( - ErrMissingSessionToken = gperr.New("missing session token") - ErrInvalidSessionToken = gperr.New("invalid session token") - ErrUserNotAllowed = gperr.New("user not allowed") + ErrMissingSessionToken = errors.New("missing session token") + ErrInvalidSessionToken = errors.New("invalid session token") + ErrUserNotAllowed = errors.New("user not allowed") ) func IsFrontend(r *http.Request) bool { diff --git a/internal/autocert/config.go b/internal/autocert/config.go index 68aafae6..e0af3b64 100644 --- a/internal/autocert/config.go +++ b/internal/autocert/config.go @@ -66,13 +66,13 @@ const ( var domainOrWildcardRE = regexp.MustCompile(`^\*?([^.]+\.)+[^.]+$`) -// Validate implements the utils.CustomValidator interface. -func (cfg *Config) Validate() gperr.Error { +// Validate implements the serialization.CustomValidator interface. +func (cfg *Config) Validate() error { seenPaths := make(map[string]int) // path -> provider idx (0 for main, 1+ for extras) return cfg.validate(seenPaths) } -func (cfg *ConfigExtra) Validate() gperr.Error { +func (cfg *ConfigExtra) Validate() error { return nil // done by main config's validate } @@ -80,7 +80,7 @@ func (cfg *ConfigExtra) AsConfig() *Config { return (*Config)(cfg) } -func (cfg *Config) validate(seenPaths map[string]int) gperr.Error { +func (cfg *Config) validate(seenPaths map[string]int) error { if cfg.Provider == "" { cfg.Provider = ProviderLocal } @@ -157,7 +157,7 @@ func (cfg *Config) validate(seenPaths map[string]int) gperr.Error { cfg.Extra[i].AsConfig().idx = i + 1 err := cfg.Extra[i].AsConfig().validate(seenPaths) if err != nil { - b.Add(err.Subjectf("extra[%d]", i)) + b.AddSubjectf(err, "extra[%d]", i) } } } @@ -179,10 +179,10 @@ func (cfg *Config) GetLegoConfig() (*User, *lego.Config, error) { log.Info().Err(err).Msg("failed to load ACME private key, generating a now one") privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return nil, nil, gperr.New("generate ACME private key").With(err) + return nil, nil, fmt.Errorf("generate ACME private key: %w", err) } if err = cfg.SaveACMEKey(privKey); err != nil { - return nil, nil, gperr.New("save ACME private key").With(err) + return nil, nil, fmt.Errorf("save ACME private key: %w", err) } } } @@ -206,7 +206,7 @@ func (cfg *Config) GetLegoConfig() (*User, *lego.Config, error) { if len(cfg.CACerts) > 0 { certPool, err := lego.CreateCertPool(cfg.CACerts, true) if err != nil { - return nil, nil, gperr.New("failed to create cert pool").With(err) + return nil, nil, fmt.Errorf("failed to create cert pool: %w", err) } legoCfg.HTTPClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = certPool } diff --git a/internal/autocert/provider.go b/internal/autocert/provider.go index 3c84f906..b870981b 100644 --- a/internal/autocert/provider.go +++ b/internal/autocert/provider.go @@ -150,7 +150,7 @@ func (p *Provider) GetName() string { } func (p *Provider) fmtError(err error) error { - return gperr.PrependSubject(fmt.Sprintf("provider: %s", p.GetName()), err) + return gperr.PrependSubject(err, "provider: "+p.GetName()) } func (p *Provider) GetCertPath() string { @@ -216,7 +216,7 @@ func (p *Provider) ObtainCertIfNotExistsAll() error { for _, provider := range p.allProviders() { errs.Go(func() error { if err := provider.obtainCertIfNotExists(); err != nil { - return fmt.Errorf("failed to obtain cert for %s: %w", provider.GetName(), err) + return gperr.PrependSubject(err, provider.GetName()) } return nil }) @@ -475,7 +475,7 @@ func (p *Provider) scheduleRenewal(parent task.Parent) { renewed, err := p.renew(renewMode) if err != nil { - gperr.LogWarn("autocert: cert renew failed", p.fmtError(err)) + log.Warn().Err(p.fmtError(err)).Msg("autocert: cert renew failed") notif.Notify(¬if.LogMessage{ Level: zerolog.ErrorLevel, Title: fmt.Sprintf("SSL certificate renewal failed for %s", p.GetName()), @@ -494,7 +494,7 @@ func (p *Provider) scheduleRenewal(parent task.Parent) { // Reset on success if err := p.ClearLastFailure(); err != nil { - gperr.LogWarn("autocert: failed to clear last failure", p.fmtError(err)) + log.Warn().Err(p.fmtError(err)).Msg("autocert: failed to clear last failure") } timer.Reset(time.Until(p.ShouldRenewOn())) } diff --git a/internal/autocert/providers.go b/internal/autocert/providers.go index 79895ba8..6d3cd38f 100644 --- a/internal/autocert/providers.go +++ b/internal/autocert/providers.go @@ -3,11 +3,10 @@ package autocert import ( "github.com/go-acme/lego/v4/challenge" "github.com/yusing/godoxy/internal/serialization" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" ) -type Generator func(map[string]strutils.Redacted) (challenge.Provider, gperr.Error) +type Generator func(map[string]strutils.Redacted) (challenge.Provider, error) var Providers = make(map[string]Generator) @@ -15,7 +14,7 @@ func DNSProvider[CT any, PT challenge.Provider]( defaultCfg func() *CT, newProvider func(*CT) (PT, error), ) Generator { - return func(opt map[string]strutils.Redacted) (challenge.Provider, gperr.Error) { + return func(opt map[string]strutils.Redacted) (challenge.Provider, error) { cfg := defaultCfg() if len(opt) > 0 { err := serialization.MapUnmarshalValidate(serialization.ToSerializedObject(opt), &cfg) @@ -24,6 +23,6 @@ func DNSProvider[CT any, PT challenge.Provider]( } } p, pErr := newProvider(cfg) - return p, gperr.Wrap(pErr) + return p, pErr } } diff --git a/internal/autocert/setup.go b/internal/autocert/setup.go index 119a8759..4091a7f0 100644 --- a/internal/autocert/setup.go +++ b/internal/autocert/setup.go @@ -4,7 +4,7 @@ import ( gperr "github.com/yusing/goutils/errs" ) -func (p *Provider) setupExtraProviders() gperr.Error { +func (p *Provider) setupExtraProviders() error { p.sniMatcher = sniMatcher{} if len(p.cfg.Extra) == 0 { return nil diff --git a/internal/config/events.go b/internal/config/events.go index ee49660c..8ec26fe0 100644 --- a/internal/config/events.go +++ b/internal/config/events.go @@ -7,6 +7,7 @@ import ( "time" "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/common" config "github.com/yusing/godoxy/internal/config/types" "github.com/yusing/godoxy/internal/notif" @@ -32,7 +33,7 @@ You may run "ls-config" to show or dump the current config.` ) func logNotifyError(action string, err error) { - gperr.LogError("config "+action+" error", err) + log.Error().Err(err).Msg("config " + action + " error") notif.Notify(¬if.LogMessage{ Level: zerolog.ErrorLevel, Title: fmt.Sprintf("Config %s error", action), @@ -41,7 +42,7 @@ func logNotifyError(action string, err error) { } func logNotifyWarn(action string, err error) { - gperr.LogWarn("config "+action+" error", err) + log.Warn().Err(err).Msg("config " + action + " warning") notif.Notify(¬if.LogMessage{ Level: zerolog.WarnLevel, Title: fmt.Sprintf("Config %s warning", action), @@ -89,7 +90,7 @@ func Load() error { return nil } -func Reload() gperr.Error { +func Reload() error { // avoid race between config change and API reload request reloadMu.Lock() defer reloadMu.Unlock() @@ -128,7 +129,7 @@ func WatchChanges() { t, configEventFlushInterval, OnConfigChange, - func(err gperr.Error) { + func(err error) { logNotifyError("reload", err) }, ) diff --git a/internal/config/state.go b/internal/config/state.go index 934d3bbf..cc19ff1d 100644 --- a/internal/config/state.go +++ b/internal/config/state.go @@ -353,7 +353,7 @@ func (state *state) initProxmox() error { for _, cfg := range proxmoxCfg { errs.Go(func() error { if err := cfg.Init(state.task.Context()); err != nil { - return err.Subject(cfg.URL) + return gperr.PrependSubject(err, cfg.URL) } return nil }) @@ -377,7 +377,7 @@ func (state *state) loadRouteProviders() error { for _, a := range providers.Agents { agentErrs.Go(func() error { if err := a.Init(state.task.Context()); err != nil { - return gperr.PrependSubject(a.String(), err) + return gperr.PrependSubject(err, a.String()) } agentpool.Add(a) return nil @@ -395,7 +395,7 @@ func (state *state) loadRouteProviders() error { for _, filename := range providers.Files { p, err := route.NewFileProvider(filename) if err != nil { - errs.Add(gperr.PrependSubject(filename, err)) + errs.Add(gperr.PrependSubject(err, filename)) return err } registerProvider(p) @@ -420,7 +420,7 @@ func (state *state) loadRouteProviders() error { for _, p := range state.providers.Range { loadErrs.Go(func() error { if err := p.LoadRoutes(); err != nil { - return err.Subject(p.String()) + return gperr.PrependSubject(err, p.String()) } resultsMu.Lock() results.Addf("%-"+strconv.Itoa(lenLongestName)+"s %d routes", p.String(), p.NumRoutes()) diff --git a/internal/config/types/config.go b/internal/config/types/config.go index ba59e44d..99dc9b52 100644 --- a/internal/config/types/config.go +++ b/internal/config/types/config.go @@ -15,7 +15,6 @@ import ( "github.com/yusing/godoxy/internal/proxmox" "github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" ) type ( @@ -42,7 +41,7 @@ type ( } ) -func Validate(data []byte) gperr.Error { +func Validate(data []byte) error { var model Config return serialization.UnmarshalValidate(data, &model, yaml.Unmarshal) } diff --git a/internal/docker/label.go b/internal/docker/label.go index baf86d71..4ef0d367 100644 --- a/internal/docker/label.go +++ b/internal/docker/label.go @@ -1,6 +1,7 @@ package docker import ( + "errors" "fmt" "strconv" "strings" @@ -11,7 +12,7 @@ import ( strutils "github.com/yusing/goutils/strings" ) -var ErrInvalidLabel = gperr.New("invalid label") +var ErrInvalidLabel = errors.New("invalid label") const nsProxyDot = NSProxy + "." @@ -23,7 +24,7 @@ var refPrefixes = func() []string { return prefixes }() -func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, gperr.Error) { +func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, error) { nestedMap := make(types.LabelMap) errs := gperr.NewBuilder("labels error") @@ -35,7 +36,7 @@ func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, g continue } if len(parts) == 1 { - errs.Add(ErrInvalidLabel.Subject(lbl)) + errs.AddSubject(ErrInvalidLabel, lbl) continue } parts = parts[1:] diff --git a/internal/health/monitor/README.md b/internal/health/monitor/README.md index 51841d89..e8e8d614 100644 --- a/internal/health/monitor/README.md +++ b/internal/health/monitor/README.md @@ -41,7 +41,7 @@ type HealthCheckFunc func(url *url.URL) (result types.HealthCheckResult, err err ```go type HealthMonitor interface { - Start(parent task.Parent) gperr.Error + Start(parent task.Parent) error Task() *task.Task Finish(reason any) UpdateURL(url *url.URL) diff --git a/internal/health/monitor/monitor.go b/internal/health/monitor/monitor.go index 2e019403..68a6de8d 100644 --- a/internal/health/monitor/monitor.go +++ b/internal/health/monitor/monitor.go @@ -2,6 +2,7 @@ package monitor import ( "context" + "errors" "fmt" "math/rand" "net/url" @@ -13,7 +14,6 @@ import ( config "github.com/yusing/godoxy/internal/config/types" "github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" "github.com/yusing/goutils/synk" "github.com/yusing/goutils/task" @@ -42,7 +42,7 @@ type ( } ) -var ErrNegativeInterval = gperr.New("negative interval") +var ErrNegativeInterval = errors.New("negative interval") func (mon *monitor) init(u *url.URL, cfg types.HealthCheckConfig, healthCheckFunc HealthCheckFunc) { if state := config.WorkingState.Load(); state != nil { @@ -79,7 +79,7 @@ func (mon *monitor) CheckHealth() (types.HealthCheckResult, error) { } // Start implements task.TaskStarter. -func (mon *monitor) Start(parent task.Parent) gperr.Error { +func (mon *monitor) Start(parent task.Parent) error { if mon.config.Interval <= 0 { return ErrNegativeInterval } diff --git a/internal/homepage/icons/README.md b/internal/homepage/icons/README.md index 800affe1..012dd545 100644 --- a/internal/homepage/icons/README.md +++ b/internal/homepage/icons/README.md @@ -176,7 +176,7 @@ func (icon *Meta) Filenames(ref string) []string func NewURL(source Source, refOrName, format string) *URL // ErrInvalidIconURL is returned when icon URL parsing fails -var ErrInvalidIconURL = gperr.New("invalid icon url") +var ErrInvalidIconURL = errors.New("invalid icon url") ``` ### Provider Interface diff --git a/internal/homepage/icons/url.go b/internal/homepage/icons/url.go index 271b6b7d..7e99bfd8 100644 --- a/internal/homepage/icons/url.go +++ b/internal/homepage/icons/url.go @@ -1,6 +1,7 @@ package icons import ( + "errors" "fmt" "strings" @@ -40,7 +41,7 @@ const ( VariantDark Variant = "dark" ) -var ErrInvalidIconURL = gperr.New("invalid icon url") +var ErrInvalidIconURL = errors.New("invalid icon url") func NewURL(source Source, refOrName, format string) *URL { switch source { @@ -119,7 +120,7 @@ func (u *URL) parse(v string, checkExists bool) error { case "@target", "": // @target/favicon.ico, /favicon.ico url := v[slashIndex:] if url == "/" { - return ErrInvalidIconURL.Withf("%s", "empty path") + return fmt.Errorf("%w: empty path", ErrInvalidIconURL) } u.FullURL = &url u.Source = SourceRelative @@ -131,7 +132,7 @@ func (u *URL) parse(v string, checkExists bool) error { } parts := strings.Split(v[slashIndex+1:], ".") if len(parts) != 2 { - return ErrInvalidIconURL.Withf("expect @%s/., e.g. @%s/adguard-home.webp", beforeSlash, beforeSlash) + return fmt.Errorf("%w: expect %s/., e.g. %s/adguard-home.webp", ErrInvalidIconURL, beforeSlash, beforeSlash) } reference, format := parts[0], strings.ToLower(parts[1]) if reference == "" || format == "" { @@ -140,7 +141,7 @@ func (u *URL) parse(v string, checkExists bool) error { switch format { case "svg", "png", "webp": default: - return ErrInvalidIconURL.Withf("%s", "invalid image format, expect svg/png/webp") + return fmt.Errorf("%w: invalid image format, expect svg/png/webp", ErrInvalidIconURL) } isLight, isDark := false, false if strings.HasSuffix(reference, "-light") { @@ -158,10 +159,10 @@ func (u *URL) parse(v string, checkExists bool) error { IsDark: isDark, } if checkExists && !u.HasIcon() { - return ErrInvalidIconURL.Withf("no such icon %s.%s from %s", reference, format, u.Source) + return fmt.Errorf("%w: no such icon %s.%s from %s", ErrInvalidIconURL, reference, format, u.Source) } default: - return ErrInvalidIconURL.Subject(v) + return gperr.PrependSubject(ErrInvalidIconURL, v) } return nil diff --git a/internal/homepage/integrations/qbittorrent/client.go b/internal/homepage/integrations/qbittorrent/client.go index 4ec5df01..756ceb77 100644 --- a/internal/homepage/integrations/qbittorrent/client.go +++ b/internal/homepage/integrations/qbittorrent/client.go @@ -2,13 +2,13 @@ package qbittorrent import ( "context" + "fmt" "io" "net/http" "net/url" "github.com/bytedance/sonic" "github.com/yusing/godoxy/internal/homepage/widgets" - gperr "github.com/yusing/goutils/errs" ) type Client struct { @@ -46,7 +46,7 @@ func (c *Client) doRequest(ctx context.Context, method, endpoint string, query u } if resp.StatusCode != http.StatusOK { - return nil, gperr.Errorf("%w: %d %s", widgets.ErrHTTPStatus, resp.StatusCode, resp.Status) + return nil, fmt.Errorf("%w: %d %s", widgets.ErrHTTPStatus, resp.StatusCode, resp.Status) } return resp, nil diff --git a/internal/homepage/widgets/README.md b/internal/homepage/widgets/README.md index c52d62d6..e2a2b719 100644 --- a/internal/homepage/widgets/README.md +++ b/internal/homepage/widgets/README.md @@ -50,8 +50,7 @@ const ( ### Errors ```go -var ErrInvalidProvider = gperr.New("invalid provider") -var ErrHTTPStatus = gperr.New("http status") +var ErrInvalidProvider = errors.New("invalid provider") ``` ## API Reference diff --git a/internal/homepage/widgets/http.go b/internal/homepage/widgets/http.go index f4b71982..59e168ca 100644 --- a/internal/homepage/widgets/http.go +++ b/internal/homepage/widgets/http.go @@ -1,14 +1,13 @@ package widgets import ( + "errors" "net/http" "time" - - gperr "github.com/yusing/goutils/errs" ) var HTTPClient = &http.Client{ Timeout: 10 * time.Second, } -var ErrHTTPStatus = gperr.New("http status") +var ErrHTTPStatus = errors.New("http status") diff --git a/internal/homepage/widgets/widgets.go b/internal/homepage/widgets/widgets.go index 5ee440ea..0580cfcb 100644 --- a/internal/homepage/widgets/widgets.go +++ b/internal/homepage/widgets/widgets.go @@ -2,6 +2,8 @@ package widgets import ( "context" + "errors" + "fmt" "github.com/yusing/godoxy/internal/serialization" gperr "github.com/yusing/goutils/errs" @@ -30,21 +32,21 @@ var widgetProviders = map[string]struct{}{ WidgetProviderQbittorrent: {}, } -var ErrInvalidProvider = gperr.New("invalid provider") +var ErrInvalidProvider = errors.New("invalid provider") func (cfg *Config) UnmarshalMap(m map[string]any) error { var ok bool cfg.Provider, ok = m["provider"].(string) if !ok { - return ErrInvalidProvider.Withf("non string") + return fmt.Errorf("%w: non string", ErrInvalidProvider) } if _, ok := widgetProviders[cfg.Provider]; !ok { - return ErrInvalidProvider.Subject(cfg.Provider) + return gperr.PrependSubject(ErrInvalidProvider, cfg.Provider) } delete(m, "provider") m, ok = m["config"].(map[string]any) if !ok { - return gperr.New("invalid config") + return errors.New("invalid config") } return serialization.MapUnmarshalValidate(m, &cfg.Config) } diff --git a/internal/idlewatcher/README.md b/internal/idlewatcher/README.md index 56af1d86..f73eb0d1 100644 --- a/internal/idlewatcher/README.md +++ b/internal/idlewatcher/README.md @@ -70,7 +70,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig) func (w *Watcher) Wake(ctx context.Context) error // Start begins the idle watcher loop -func (w *Watcher) Start(parent task.Parent) gperr.Error +func (w *Watcher) Start(parent task.Parent) error // ServeHTTP serves the loading page and SSE events func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) @@ -103,7 +103,7 @@ var ( classDiagram class Watcher { +Wake(ctx) error - +Start(parent) gperr.Error + +Start(parent) error +ServeHTTP(ResponseWriter, *Request) +ListenAndServe(ctx, preDial, onRead) +Key() string diff --git a/internal/idlewatcher/errors.go b/internal/idlewatcher/errors.go index 3254f6f6..d855050a 100644 --- a/internal/idlewatcher/errors.go +++ b/internal/idlewatcher/errors.go @@ -20,7 +20,7 @@ func (e *watcherError) Error() string { } func (w *Watcher) newWatcherError(err error) error { - if errors.Is(err, causeReload) { + if errors.Is(err, errCauseReload) { return nil } if wErr, ok := err.(*watcherError); ok { //nolint:errorlint @@ -44,7 +44,7 @@ func (e *depError) Error() string { } func (w *Watcher) newDepError(action string, dep *dependency, err error) error { - if errors.Is(err, causeReload) { + if errors.Is(err, errCauseReload) { return nil } if dErr, ok := err.(*depError); ok { //nolint:errorlint diff --git a/internal/idlewatcher/handle_http.go b/internal/idlewatcher/handle_http.go index 2778c0e8..02c31b03 100644 --- a/internal/idlewatcher/handle_http.go +++ b/internal/idlewatcher/handle_http.go @@ -7,10 +7,10 @@ import ( "net/http" "strconv" + "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/homepage/icons" iconfetch "github.com/yusing/godoxy/internal/homepage/icons/fetch" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" - gperr "github.com/yusing/goutils/errs" httputils "github.com/yusing/goutils/http" _ "unsafe" @@ -79,7 +79,7 @@ func (w *Watcher) handleWakeEventsSSE(rw http.ResponseWriter, r *http.Request) { default: err := errors.Join(event.WriteSSE(rw), controller.Flush()) if err != nil { - gperr.LogError("Failed to write SSE event", err, &w.l) + log.Err(err).Msg("Failed to write SSE event") return } } @@ -91,7 +91,7 @@ func (w *Watcher) handleWakeEventsSSE(rw http.ResponseWriter, r *http.Request) { case event := <-eventCh: err := errors.Join(event.WriteSSE(rw), controller.Flush()) if err != nil { - gperr.LogError("Failed to write SSE event", err, &w.l) + log.Err(err).Msg("Failed to write SSE event") return } case <-ctx.Done(): @@ -169,7 +169,7 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN err := w.Wake(r.Context()) if err != nil { - gperr.LogError("Failed to wake container", err, &w.l) + log.Err(err).Msg("Failed to wake container") if !acceptHTML { http.Error(rw, "Failed to wake container", http.StatusInternalServerError) return false diff --git a/internal/idlewatcher/health.go b/internal/idlewatcher/health.go index 64fa070d..dc7fc5ae 100644 --- a/internal/idlewatcher/health.go +++ b/internal/idlewatcher/health.go @@ -1,16 +1,16 @@ package idlewatcher import ( + "fmt" "time" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" ) // Start implements health.HealthMonitor. -func (w *Watcher) Start(parent task.Parent) gperr.Error { +func (w *Watcher) Start(parent task.Parent) error { w.task.OnCancel("route_cleanup", func() { parent.Finish(w.task.FinishCause()) }) @@ -113,7 +113,7 @@ func (w *Watcher) checkUpdateState() (ready bool, err error) { if !state.startedAt.IsZero() { elapsed := time.Since(state.startedAt) if elapsed > w.cfg.WakeTimeout { - err := gperr.Errorf("container failed to become ready within %v (started at %v, %d health check attempts)", + err := fmt.Errorf("container failed to become ready within %v (started at %v, %d health check attempts)", w.cfg.WakeTimeout, state.startedAt, state.healthTries) w.l.Error(). Dur("elapsed", elapsed). diff --git a/internal/idlewatcher/provider/docker.go b/internal/idlewatcher/provider/docker.go index 5c491ff2..88bfdd9b 100644 --- a/internal/idlewatcher/provider/docker.go +++ b/internal/idlewatcher/provider/docker.go @@ -2,6 +2,7 @@ package provider import ( "context" + "fmt" "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" @@ -9,7 +10,6 @@ import ( idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" "github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/watcher" - gperr "github.com/yusing/goutils/errs" ) type DockerProvider struct { @@ -75,10 +75,10 @@ func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.Conta case container.StatePaused: return idlewatcher.ContainerStatusPaused, nil } - return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status.Container.State.Status)) + return idlewatcher.ContainerStatusError, fmt.Errorf("%w: %s", idlewatcher.ErrUnexpectedContainerStatus, status.Container.State.Status) } -func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan gperr.Error) { +func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan error) { return p.watcher.EventsWithOptions(ctx, watcher.DockerListOptions{ Filters: watcher.NewDockerFilters( watcher.DockerFilterContainer, diff --git a/internal/idlewatcher/provider/proxmox.go b/internal/idlewatcher/provider/proxmox.go index 0b7e5b19..5357c399 100644 --- a/internal/idlewatcher/provider/proxmox.go +++ b/internal/idlewatcher/provider/proxmox.go @@ -2,6 +2,8 @@ package provider import ( "context" + "errors" + "fmt" "strconv" "time" @@ -27,7 +29,7 @@ var ErrNodeNotFound = gperr.New("node not found in pool") func NewProxmoxProvider(ctx context.Context, nodeName string, vmid int) (idlewatcher.Provider, error) { if nodeName == "" || vmid == 0 { - return nil, gperr.New("node name and vmid are required") + return nil, errors.New("node name and vmid are required") } node, ok := proxmox.Nodes.Get(nodeName) @@ -77,12 +79,12 @@ func (p *ProxmoxProvider) ContainerStatus(ctx context.Context) (idlewatcher.Cont case proxmox.LXCStatusStopped: return idlewatcher.ContainerStatusStopped, nil } - return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status)) + return idlewatcher.ContainerStatusError, fmt.Errorf("%w: %s", idlewatcher.ErrUnexpectedContainerStatus, string(status)) } -func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-chan gperr.Error) { +func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-chan error) { eventCh := make(chan watcher.Event) - errCh := make(chan gperr.Error) + errCh := make(chan error) go func() { defer close(eventCh) @@ -91,7 +93,7 @@ func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-ch var err error p.running, err = p.LXCIsRunning(ctx, p.vmid) if err != nil { - errCh <- gperr.Wrap(err) + errCh <- err return } @@ -110,7 +112,7 @@ func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-ch case <-ticker.C: status, err := p.ContainerStatus(ctx) if err != nil { - errCh <- gperr.Wrap(err) + errCh <- err return } running := status == idlewatcher.ContainerStatusRunning diff --git a/internal/idlewatcher/types/container_status.go b/internal/idlewatcher/types/container_status.go index c2f13b76..6e2ac644 100644 --- a/internal/idlewatcher/types/container_status.go +++ b/internal/idlewatcher/types/container_status.go @@ -1,6 +1,6 @@ package idlewatcher -import gperr "github.com/yusing/goutils/errs" +import "errors" type ContainerStatus string @@ -11,4 +11,4 @@ const ( ContainerStatusStopped ContainerStatus = "stopped" ) -var ErrUnexpectedContainerStatus = gperr.New("unexpected container status") +var ErrUnexpectedContainerStatus = errors.New("unexpected container status") diff --git a/internal/idlewatcher/types/provider.go b/internal/idlewatcher/types/provider.go index 66af96a4..9806c24b 100644 --- a/internal/idlewatcher/types/provider.go +++ b/internal/idlewatcher/types/provider.go @@ -5,7 +5,6 @@ import ( "github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/watcher/events" - gperr "github.com/yusing/goutils/errs" ) type Provider interface { @@ -15,6 +14,6 @@ type Provider interface { ContainerStop(ctx context.Context, signal types.ContainerSignal, timeout int) error ContainerKill(ctx context.Context, signal types.ContainerSignal) error ContainerStatus(ctx context.Context) (ContainerStatus, error) - Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan gperr.Error) + Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan error) Close() } diff --git a/internal/idlewatcher/watcher.go b/internal/idlewatcher/watcher.go index 6236de0f..907730b1 100644 --- a/internal/idlewatcher/watcher.go +++ b/internal/idlewatcher/watcher.go @@ -3,6 +3,7 @@ package idlewatcher import ( "context" "errors" + "fmt" "math" "strings" "sync" @@ -97,8 +98,8 @@ const ( ) var ( - causeReload = gperr.New("reloaded") //nolint:errname - causeContainerDestroy = gperr.New("container destroyed") //nolint:errname + errCauseReload = errors.New("reloaded") + errCauseContainerDestroy = errors.New("container destroyed") ) const reqTimeout = 3 * time.Second @@ -286,7 +287,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig) w.stream = r.Stream() default: p.Close() - return nil, w.newWatcherError(gperr.Errorf("unexpected route type: %T", r)) + return nil, w.newWatcherError(fmt.Errorf("unexpected route type: %T", r)) } w.route = r @@ -320,12 +321,12 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig) delete(watcherMap, key) watcherMapMu.Unlock() - if errors.Is(cause, causeReload) { + if errors.Is(cause, errCauseReload) { // no log - } else if errors.Is(cause, causeContainerDestroy) || errors.Is(cause, task.ErrProgramExiting) || errors.Is(cause, config.ErrConfigChanged) { + } else if errors.Is(cause, errCauseContainerDestroy) || errors.Is(cause, task.ErrProgramExiting) || errors.Is(cause, config.ErrConfigChanged) { w.l.Info().Msg("idlewatcher stopped") } else { - gperr.LogError("idlewatcher stopped unexpectedly", cause, &w.l) + w.l.Err(cause).Msg("idlewatcher stopped unexpectedly") } w.idleTicker.Stop() @@ -467,7 +468,7 @@ func (w *Watcher) wakeIfStopped(ctx context.Context) error { defer cancel() p := w.provider.Load() if p == nil { - return gperr.Errorf("provider not set") + return errors.New("provider not set") } switch state.status { case idlewatcher.ContainerStatusStopped: @@ -477,7 +478,7 @@ func (w *Watcher) wakeIfStopped(ctx context.Context) error { w.sendEvent(WakeEventStarting, w.cfg.ContainerName()+" is unpausing...", nil) return p.ContainerUnpause(ctx) default: - return gperr.Errorf("unexpected container status: %s", state.status) + return fmt.Errorf("unexpected container status: %s", state.status) } } @@ -512,7 +513,7 @@ func (w *Watcher) stopByMethod() error { var err error p := w.provider.Load() if p == nil { - return gperr.New("provider not set") + return errors.New("provider not set") } switch cfg.StopMethod { case types.ContainerStopMethodPause: @@ -522,7 +523,7 @@ func (w *Watcher) stopByMethod() error { case types.ContainerStopMethodKill: err = p.ContainerKill(ctx, cfg.StopSignal) default: - err = w.newWatcherError(gperr.Errorf("unexpected stop method: %q", cfg.StopMethod)) + err = w.newWatcherError(fmt.Errorf("unexpected stop method: %q", cfg.StopMethod)) } if err != nil { @@ -564,7 +565,7 @@ func (w *Watcher) expires() time.Time { func (w *Watcher) watchUntilDestroy() (returnCause error) { p := w.provider.Load() if p == nil { - return gperr.Errorf("provider not set") + return errors.New("provider not set") } defer p.Close() eventCh, errCh := p.Watch(w.Task().Context()) @@ -572,14 +573,14 @@ func (w *Watcher) watchUntilDestroy() (returnCause error) { for { select { case <-w.task.Context().Done(): - return gperr.Wrap(w.task.FinishCause()) + return w.task.FinishCause() case err := <-errCh: - gperr.LogError("watcher error", err, &w.l) + w.l.Err(err).Msg("watcher error") case e := <-eventCh: w.l.Debug().Stringer("action", e.Action).Msg("state changed") switch e.Action { case events.ActionContainerDestroy: - return causeContainerDestroy + return errCauseContainerDestroy case events.ActionForceReload: continue } diff --git a/internal/logging/accesslog/config.go b/internal/logging/accesslog/config.go index 1200ba2f..944fd41b 100644 --- a/internal/logging/accesslog/config.go +++ b/internal/logging/accesslog/config.go @@ -1,11 +1,11 @@ package accesslog import ( + "errors" "net/http" "time" "github.com/yusing/godoxy/internal/serialization" - gperr "github.com/yusing/goutils/errs" ) type ( @@ -58,9 +58,9 @@ var ( ReqLoggerFormats = []Format{FormatCommon, FormatCombined, FormatJSON} ) -func (cfg *ConfigBase) Validate() gperr.Error { +func (cfg *ConfigBase) Validate() error { if cfg.Path == "" && !cfg.Stdout { - return gperr.New("path or stdout is required") + return errors.New("path or stdout is required") } return nil } diff --git a/internal/logging/accesslog/file_access_logger.go b/internal/logging/accesslog/file_access_logger.go index 74bcf0f7..5f07e05b 100644 --- a/internal/logging/accesslog/file_access_logger.go +++ b/internal/logging/accesslog/file_access_logger.go @@ -1,6 +1,7 @@ package accesslog import ( + "fmt" "io" "net/http" "sync" @@ -11,7 +12,6 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" maxmind "github.com/yusing/godoxy/internal/maxmind/types" - gperr "github.com/yusing/goutils/errs" ioutils "github.com/yusing/goutils/io" strutils "github.com/yusing/goutils/strings" "github.com/yusing/goutils/synk" @@ -161,9 +161,9 @@ func (l *fileAccessLogger) Rotate(result *RotateResult) (rotated bool, err error func (l *fileAccessLogger) handleErr(err error) { if l.errRateLimiter.Allow() { - gperr.LogError("failed to write access log", err, &l.logger) + l.logger.Err(err).Msg("failed to write access log") } else { - gperr.LogError("too many errors, stopping access log", err, &l.logger) + l.logger.Err(err).Msg("too many errors, stopping access log") l.task.Finish(err) } } @@ -234,7 +234,7 @@ func (l *fileAccessLogger) write(data []byte) { if err != nil { l.handleErr(err) } else if n < len(data) { - l.handleErr(gperr.Errorf("%w, writing %d bytes, only %d written", io.ErrShortWrite, len(data), n)) + l.handleErr(fmt.Errorf("%w, writing %d bytes, only %d written", io.ErrShortWrite, len(data), n)) } atomic.AddInt64(&l.writeCount, int64(n)) } diff --git a/internal/logging/accesslog/filter.go b/internal/logging/accesslog/filter.go index 5352174e..d77d3b84 100644 --- a/internal/logging/accesslog/filter.go +++ b/internal/logging/accesslog/filter.go @@ -1,12 +1,13 @@ package accesslog import ( + "errors" + "fmt" "net" "net/http" "strings" nettypes "github.com/yusing/godoxy/internal/net/types" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" ) @@ -29,7 +30,7 @@ type ( } // @name CIDR ) -var ErrInvalidHTTPHeaderFilter = gperr.New("invalid http header filter") +var ErrInvalidHTTPHeaderFilter = errors.New("invalid http header filter") func (f *LogFilter[T]) CheckKeep(req *http.Request, res *http.Response) bool { if len(f.Values) == 0 { @@ -59,7 +60,7 @@ func (k *HTTPHeader) Parse(v string) error { split = append(split, "") case 2: default: - return ErrInvalidHTTPHeaderFilter.Subject(v) + return fmt.Errorf("%w: %s", ErrInvalidHTTPHeaderFilter, v) } k.Key = split[0] k.Value = split[1] diff --git a/internal/logging/accesslog/retention.go b/internal/logging/accesslog/retention.go index a67cf953..7033d61f 100644 --- a/internal/logging/accesslog/retention.go +++ b/internal/logging/accesslog/retention.go @@ -1,10 +1,10 @@ package accesslog import ( + "errors" "fmt" "strconv" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" ) @@ -15,8 +15,8 @@ type Retention struct { } // @name LogRetention var ( - ErrInvalidSyntax = gperr.New("invalid syntax") - ErrZeroValue = gperr.New("zero value") + ErrInvalidSyntax = errors.New("invalid syntax") + ErrZeroValue = errors.New("zero value") ) // see back_scanner_test.go#L210 for benchmarks @@ -34,7 +34,7 @@ var defaultChunkSize = 32 * kilobyte func (r *Retention) Parse(v string) (err error) { split := strutils.SplitSpace(v) if len(split) != 2 { - return ErrInvalidSyntax.Subject(v) + return fmt.Errorf("%w: %s", ErrInvalidSyntax, v) } switch split[0] { case "last": @@ -64,7 +64,7 @@ func (r *Retention) Parse(v string) (err error) { case "GB": r.KeepSize = n * gigabyte default: - return ErrInvalidSyntax.Subject("unit " + split[1]) + return fmt.Errorf("%w: unit %s", ErrInvalidSyntax, split[1]) } } if !r.IsValid() { diff --git a/internal/logging/accesslog/status_code_range.go b/internal/logging/accesslog/status_code_range.go index 9c26d987..45341df5 100644 --- a/internal/logging/accesslog/status_code_range.go +++ b/internal/logging/accesslog/status_code_range.go @@ -1,6 +1,8 @@ package accesslog import ( + "errors" + "fmt" "strconv" gperr "github.com/yusing/goutils/errs" @@ -12,7 +14,7 @@ type StatusCodeRange struct { End int } // @name StatusCodeRange -var ErrInvalidStatusCodeRange = gperr.New("invalid status code range") +var ErrInvalidStatusCodeRange = errors.New("invalid status code range") func (r *StatusCodeRange) Includes(code int) bool { return r.Start <= code && code <= r.End @@ -25,7 +27,7 @@ func (r *StatusCodeRange) Parse(v string) error { case 1: start, err := strconv.Atoi(split[0]) if err != nil { - return gperr.Wrap(err) + return err } r.Start = start r.End = start @@ -40,7 +42,7 @@ func (r *StatusCodeRange) Parse(v string) error { r.End = end return nil default: - return ErrInvalidStatusCodeRange.Subject(v) + return fmt.Errorf("%w: %s", ErrInvalidStatusCodeRange, v) } } diff --git a/internal/logging/logging.go b/internal/logging/logging.go index ab9deb90..29225d48 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -87,8 +87,28 @@ func multiWriter(out ...io.Writer) io.Writer { func NewLogger(out ...io.Writer) zerolog.Logger { writer := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { - w.Out = diodeMultiWriter(out...) + if !common.IsTest { + w.Out = diodeMultiWriter(out...) + } else { + w.Out = multiWriter(out...) + } w.TimeFormat = timeFmt + w.FormatPrepare = func(evt map[string]any) error { + // move error field to join message if it's multiline + if err, ok := evt[zerolog.ErrorFieldName].(string); ok { + if strings.Count(err, "\n") == 0 { + return nil + } + msg, ok := evt[zerolog.MessageFieldName].(string) + if ok && msg != "" { + evt[zerolog.MessageFieldName] = msg + "\n" + err + } else { + evt[zerolog.MessageFieldName] = err + } + delete(evt, zerolog.ErrorFieldName) + } + return nil + } w.FormatMessage = func(msgI any) string { // pad spaces for each line if msgI == nil { return "" diff --git a/internal/maxmind/README.md b/internal/maxmind/README.md index 2791e799..f98b3369 100644 --- a/internal/maxmind/README.md +++ b/internal/maxmind/README.md @@ -99,7 +99,7 @@ type Location struct { ```go // LoadMaxMindDB loads or downloads the MaxMind database. -func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error +func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) error ``` ### Lookup @@ -324,8 +324,8 @@ The maxmind package integrates with: ```go var ( - ErrResponseNotOK = gperr.New("response not OK") - ErrDownloadFailure = gperr.New("download failure") + ErrResponseNotOK = errors.New("response not OK") + ErrDownloadFailure = errors.New("download failure") ) ``` diff --git a/internal/maxmind/instance.go b/internal/maxmind/instance.go index 056417c4..fd575622 100644 --- a/internal/maxmind/instance.go +++ b/internal/maxmind/instance.go @@ -6,7 +6,6 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/notif" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" ) @@ -24,7 +23,7 @@ func warnNotConfigured() { }) } -func SetInstance(parent task.Parent, cfg *Config) gperr.Error { +func SetInstance(parent task.Parent, cfg *Config) error { newInstance := &MaxMind{Config: cfg} if err := newInstance.LoadMaxMindDB(parent); err != nil { return err diff --git a/internal/maxmind/maxmind.go b/internal/maxmind/maxmind.go index a487cd83..0ab8e77e 100644 --- a/internal/maxmind/maxmind.go +++ b/internal/maxmind/maxmind.go @@ -16,7 +16,6 @@ import ( "github.com/oschwald/maxminddb-golang" "github.com/yusing/godoxy/internal/common" maxmind "github.com/yusing/godoxy/internal/maxmind/types" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" ) @@ -52,8 +51,8 @@ var httpClient = &http.Client{ } var ( - ErrResponseNotOK = gperr.New("response not OK") - ErrDownloadFailure = gperr.New("download failure") + ErrResponseNotOK = errors.New("response not OK") + ErrDownloadFailure = errors.New("download failure") ) func (cfg *MaxMind) dbPath() string { @@ -74,7 +73,7 @@ func (cfg *MaxMind) dbFilename() string { return "GeoIP2-Country.mmdb" } -func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error { +func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) error { if cfg.Database == "" { return nil } @@ -92,7 +91,7 @@ func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error { // ignore invalid error, just download it again var invalidErr maxminddb.InvalidDatabaseError if !errors.As(err, &invalidErr) { - return gperr.Wrap(err) + return err } } valid = false @@ -101,7 +100,7 @@ func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error { if !valid { cfg.Logger().Info().Msg("MaxMind DB not found/invalid, downloading...") if err = cfg.download(); err != nil { - return ErrDownloadFailure.With(err) + return fmt.Errorf("%w: %w", ErrDownloadFailure, err) } } else { cfg.Logger().Info().Msg("MaxMind DB loaded") @@ -236,7 +235,7 @@ func (cfg *MaxMind) download() error { // extract .tar.gz and to database err = extractFileFromTarGz(databaseGZ, cfg.dbFilename(), tmpDBPath) if err != nil { - return gperr.New("failed to extract database from archive").With(err) + return err } // test if the downloaded database is valid diff --git a/internal/maxmind/types/config.go b/internal/maxmind/types/config.go index 16003029..977d070b 100644 --- a/internal/maxmind/types/config.go +++ b/internal/maxmind/types/config.go @@ -3,7 +3,6 @@ package maxmind import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" ) @@ -21,7 +20,7 @@ const ( MaxMindGeoIP2 DatabaseType = "geoip2" ) -func (cfg *Config) Validate() gperr.Error { +func (cfg *Config) Validate() error { if cfg.Database == "" { cfg.Database = MaxMindGeoLite } diff --git a/internal/metrics/period/poller.go b/internal/metrics/period/poller.go index d5efb577..655df900 100644 --- a/internal/metrics/period/poller.go +++ b/internal/metrics/period/poller.go @@ -3,7 +3,6 @@ package period import ( "context" "encoding/json" - "fmt" "net/url" "os" "path/filepath" @@ -196,7 +195,7 @@ func (p *Poller[T, AggregateT]) Start(parent task.Parent) { if tickCount%gatherErrsTicks == 0 { errs, ok := p.gatherErrs() if ok { - gperr.LogError(fmt.Sprintf("poller %s has encountered %d errors in the last %s:", p.name, len(p.errs), gatherErrsInterval), errs) + log.Err(errs).Msgf("poller %s has encountered %d errors in the last %s:", p.name, len(p.errs), gatherErrsInterval) } p.clearErrs() } diff --git a/internal/net/gphttp/loadbalancer/README.md b/internal/net/gphttp/loadbalancer/README.md index 97432657..58425276 100644 --- a/internal/net/gphttp/loadbalancer/README.md +++ b/internal/net/gphttp/loadbalancer/README.md @@ -102,7 +102,7 @@ type LoadBalancer struct { func New(cfg *types.LoadBalancerConfig) *LoadBalancer // Start the load balancer as a background task -func (lb *LoadBalancer) Start(parent task.Parent) gperr.Error +func (lb *LoadBalancer) Start(parent task.Parent) error // Update configuration dynamically func (lb *LoadBalancer) UpdateConfigIfNeeded(cfg *types.LoadBalancerConfig) diff --git a/internal/net/gphttp/loadbalancer/ip_hash.go b/internal/net/gphttp/loadbalancer/ip_hash.go index 710757c6..907646ac 100644 --- a/internal/net/gphttp/loadbalancer/ip_hash.go +++ b/internal/net/gphttp/loadbalancer/ip_hash.go @@ -9,7 +9,6 @@ import ( "github.com/bytedance/gopkg/util/xxhash3" "github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" ) type ipHash struct { @@ -28,10 +27,10 @@ func (lb *LoadBalancer) newIPHash() impl { if len(lb.Options) == 0 { return impl } - var err gperr.Error + var err error impl.realIP, err = middleware.RealIP.New(lb.Options) if err != nil { - gperr.LogError("invalid real_ip options, ignoring", err, &impl.l) + impl.l.Err(err).Msg("invalid real_ip options, ignoring") } return impl } diff --git a/internal/net/gphttp/loadbalancer/loadbalancer.go b/internal/net/gphttp/loadbalancer/loadbalancer.go index ac2b14cc..ee271cad 100644 --- a/internal/net/gphttp/loadbalancer/loadbalancer.go +++ b/internal/net/gphttp/loadbalancer/loadbalancer.go @@ -56,7 +56,7 @@ func New(cfg *types.LoadBalancerConfig) *LoadBalancer { } // Start implements task.TaskStarter. -func (lb *LoadBalancer) Start(parent task.Parent) gperr.Error { +func (lb *LoadBalancer) Start(parent task.Parent) error { lb.startTime = time.Now() lb.task = parent.Subtask("loadbalancer."+lb.Link, true) lb.task.OnCancel("cleanup", func() { @@ -234,7 +234,7 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) { }) } if err := errs.Wait().Error(); err != nil { - gperr.LogWarn("failed to wake some servers", err, &lb.l) + lb.l.Warn().Err(err).Msg("failed to wake some servers") } } diff --git a/internal/net/gphttp/middleware/captcha/README.md b/internal/net/gphttp/middleware/captcha/README.md index eeb0d57a..7e5d7650 100644 --- a/internal/net/gphttp/middleware/captcha/README.md +++ b/internal/net/gphttp/middleware/captcha/README.md @@ -249,7 +249,7 @@ The package includes an embedded HTML template (`captcha.html`) that renders the ## Error Handling ```go -var ErrCaptchaVerificationFailed = gperr.New("captcha verification failed") +var ErrCaptchaVerificationFailed = errors.New("captcha verification failed") // Verification errors are logged with request details log.Warn().Err(err).Str("url", r.URL.String()).Str("remote_addr", r.RemoteAddr).Msg("failed to verify captcha") diff --git a/internal/net/gphttp/middleware/captcha/provider.go b/internal/net/gphttp/middleware/captcha/provider.go index e8c2d470..560a1960 100644 --- a/internal/net/gphttp/middleware/captcha/provider.go +++ b/internal/net/gphttp/middleware/captcha/provider.go @@ -1,10 +1,9 @@ package captcha import ( + "errors" "net/http" "time" - - gperr "github.com/yusing/goutils/errs" ) type Provider interface { @@ -16,4 +15,4 @@ type Provider interface { FormHTML() string } -var ErrCaptchaVerificationFailed = gperr.New("captcha verification failed") +var ErrCaptchaVerificationFailed = errors.New("captcha verification failed") diff --git a/internal/net/gphttp/middleware/errorpage/error_page.go b/internal/net/gphttp/middleware/errorpage/error_page.go index c9590729..3eaede87 100644 --- a/internal/net/gphttp/middleware/errorpage/error_page.go +++ b/internal/net/gphttp/middleware/errorpage/error_page.go @@ -11,7 +11,6 @@ import ( "github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/watcher" "github.com/yusing/godoxy/internal/watcher/events" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/fs" "github.com/yusing/goutils/task" ) @@ -93,7 +92,7 @@ func watchDir() { loadContent() } case err := <-errCh: - gperr.LogError("error watching error page directory", err) + log.Err(err).Msg("error watching error page directory") } } } diff --git a/internal/net/gphttp/middleware/middleware.go b/internal/net/gphttp/middleware/middleware.go index 77fb38af..e4c80afe 100644 --- a/internal/net/gphttp/middleware/middleware.go +++ b/internal/net/gphttp/middleware/middleware.go @@ -1,6 +1,7 @@ package middleware import ( + "fmt" "maps" "net/http" "reflect" @@ -10,15 +11,12 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/serialization" - gperr "github.com/yusing/goutils/errs" httputils "github.com/yusing/goutils/http" "github.com/yusing/goutils/http/httpheaders" "github.com/yusing/goutils/http/reverseproxy" ) type ( - Error = gperr.Error - ReverseProxy = reverseproxy.ReverseProxy ProxyRequest = reverseproxy.ProxyRequest @@ -87,7 +85,7 @@ func (m *Middleware) setup() { } } -func (m *Middleware) apply(optsRaw OptionsRaw) gperr.Error { +func (m *Middleware) apply(optsRaw OptionsRaw) error { if len(optsRaw) == 0 { return nil } @@ -120,10 +118,10 @@ func (m *Middleware) finalize() error { return nil } -func (m *Middleware) New(optsRaw OptionsRaw) (*Middleware, gperr.Error) { +func (m *Middleware) New(optsRaw OptionsRaw) (*Middleware, error) { if m.construct == nil { // likely a middleware from compose if len(optsRaw) != 0 { - return nil, gperr.New("additional options not allowed for middleware").Subject(m.name) + return nil, fmt.Errorf("additional options not allowed for middleware %s", m.name) } return m, nil } @@ -133,7 +131,7 @@ func (m *Middleware) New(optsRaw OptionsRaw) (*Middleware, gperr.Error) { return nil, err } if err := mid.finalize(); err != nil { - return nil, gperr.Wrap(err) + return nil, err } mid.impl = mid.withCheckBypass() return mid, nil @@ -252,7 +250,7 @@ func (m *Middleware) LogError(req *http.Request) *zerolog.Event { Str("path", req.URL.Path) } -func PatchReverseProxy(rp *ReverseProxy, middlewaresMap map[string]OptionsRaw) (err gperr.Error) { +func PatchReverseProxy(rp *ReverseProxy, middlewaresMap map[string]OptionsRaw) (err error) { var middlewares []*Middleware middlewares, err = compileMiddlewares(middlewaresMap) if err != nil { diff --git a/internal/net/gphttp/middleware/middleware_builder.go b/internal/net/gphttp/middleware/middleware_builder.go index 738e439c..fd59e752 100644 --- a/internal/net/gphttp/middleware/middleware_builder.go +++ b/internal/net/gphttp/middleware/middleware_builder.go @@ -1,6 +1,7 @@ package middleware import ( + "errors" "fmt" "os" "path" @@ -10,7 +11,7 @@ import ( gperr "github.com/yusing/goutils/errs" ) -var ErrMissingMiddlewareUse = gperr.New("missing middleware 'use' field") +var ErrMissingMiddlewareUse = errors.New("missing middleware 'use' field") func BuildMiddlewaresFromComposeFile(filePath string, eb *gperr.Builder) map[string]*Middleware { fileContent, err := os.ReadFile(filePath) @@ -32,7 +33,7 @@ func BuildMiddlewaresFromYAML(source string, data []byte, eb *gperr.Builder) map for name, defs := range rawMap { chain, err := BuildMiddlewareFromChainRaw(name, defs) if err != nil { - eb.Add(err.Subject(source)) + eb.AddSubject(err, source) } else { middlewares[name+"@file"] = chain } @@ -40,7 +41,7 @@ func BuildMiddlewaresFromYAML(source string, data []byte, eb *gperr.Builder) map return middlewares } -func compileMiddlewares(middlewaresMap map[string]OptionsRaw) ([]*Middleware, gperr.Error) { +func compileMiddlewares(middlewaresMap map[string]OptionsRaw) ([]*Middleware, error) { middlewares := make([]*Middleware, 0, len(middlewaresMap)) var errs gperr.Builder @@ -68,7 +69,7 @@ func compileMiddlewares(middlewaresMap map[string]OptionsRaw) ([]*Middleware, gp return middlewares, errs.Error() } -func BuildMiddlewareFromMap(name string, middlewaresMap map[string]OptionsRaw) (*Middleware, gperr.Error) { +func BuildMiddlewareFromMap(name string, middlewaresMap map[string]OptionsRaw) (*Middleware, error) { compiled, err := compileMiddlewares(middlewaresMap) if err != nil { return nil, err @@ -77,7 +78,7 @@ func BuildMiddlewareFromMap(name string, middlewaresMap map[string]OptionsRaw) ( } // TODO: check conflict or duplicates. -func BuildMiddlewareFromChainRaw(name string, defs []map[string]any) (*Middleware, gperr.Error) { +func BuildMiddlewareFromChainRaw(name string, defs []map[string]any) (*Middleware, error) { var chainErr gperr.Builder chain := make([]*Middleware, 0, len(defs)) for i, def := range defs { diff --git a/internal/net/gphttp/middleware/middleware_chain.go b/internal/net/gphttp/middleware/middleware_chain.go index ad589a17..d9e2be97 100644 --- a/internal/net/gphttp/middleware/middleware_chain.go +++ b/internal/net/gphttp/middleware/middleware_chain.go @@ -2,6 +2,7 @@ package middleware import ( "net/http" + "strconv" gperr "github.com/yusing/goutils/errs" ) @@ -47,7 +48,7 @@ func (m *middlewareChain) modifyResponse(resp *http.Response) error { } for i, mr := range m.modResps { if err := mr.modifyResponse(resp); err != nil { - return gperr.Wrap(err).Subjectf("%d", i) + return gperr.PrependSubject(err, strconv.Itoa(i)) } } return nil diff --git a/internal/net/gphttp/middleware/middlewares.go b/internal/net/gphttp/middleware/middlewares.go index 76b28682..d4411521 100644 --- a/internal/net/gphttp/middleware/middlewares.go +++ b/internal/net/gphttp/middleware/middlewares.go @@ -44,15 +44,14 @@ var allMiddlewares = map[string]*Middleware{ } var ( - ErrUnknownMiddleware = gperr.New("unknown middleware") - ErrMiddlewareAlreadyExists = gperr.New("middleware with the same name already exists") + ErrUnknownMiddleware = errors.New("unknown middleware") + ErrMiddlewareAlreadyExists = errors.New("middleware with the same name already exists") ) -func Get(name string) (*Middleware, Error) { +func Get(name string) (*Middleware, error) { middleware, ok := allMiddlewares[strutils.ToLowerNoSnake(name)] if !ok { - return nil, ErrUnknownMiddleware. - Subject(name). + return nil, gperr.PrependSubject(ErrUnknownMiddleware, name). With(gperr.DoYouMeanField(name, allMiddlewares)) } return middleware, nil @@ -63,7 +62,7 @@ func All() map[string]*Middleware { } func LoadComposeFiles() { - errs := gperr.NewBuilder("middleware compile errors") + var errs gperr.Builder middlewareDefs, err := fsutils.ListFiles(common.MiddlewareComposeBasePath, 0) if err != nil { if errors.Is(err, fs.ErrNotExist) { @@ -81,7 +80,7 @@ func LoadComposeFiles() { for name, m := range mws { name = strutils.ToLowerNoSnake(name) if _, ok := allMiddlewares[name]; ok { - errs.Add(ErrMiddlewareAlreadyExists.Subject(name)) + errs.AddSubject(ErrMiddlewareAlreadyExists, name) continue } allMiddlewares[name] = m @@ -111,6 +110,6 @@ func LoadComposeFiles() { } } if errs.HasError() { - gperr.LogError(errs.About(), errs.Error()) + log.Err(errs.Error()).Msg("middleware compile errors") } } diff --git a/internal/net/gphttp/middleware/oidc.go b/internal/net/gphttp/middleware/oidc.go index 6406de4a..4ef673f4 100644 --- a/internal/net/gphttp/middleware/oidc.go +++ b/internal/net/gphttp/middleware/oidc.go @@ -8,7 +8,6 @@ import ( "sync/atomic" "github.com/yusing/godoxy/internal/auth" - gperr "github.com/yusing/goutils/errs" ) type oidcMiddleware struct { @@ -28,7 +27,7 @@ var OIDC = NewMiddleware[oidcMiddleware]() func (amw *oidcMiddleware) finalize() error { if !auth.IsOIDCEnabled() { - return gperr.New("OIDC not enabled but OIDC middleware is used") + return errors.New("OIDC not enabled but OIDC middleware is used") } return nil } diff --git a/internal/net/gphttp/middleware/test_utils.go b/internal/net/gphttp/middleware/test_utils.go index 105540e1..f920c69d 100644 --- a/internal/net/gphttp/middleware/test_utils.go +++ b/internal/net/gphttp/middleware/test_utils.go @@ -11,7 +11,6 @@ import ( "github.com/bytedance/sonic" "github.com/yusing/godoxy/internal/common" nettypes "github.com/yusing/godoxy/internal/net/types" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/http/reverseproxy" ) @@ -121,7 +120,7 @@ func (args *testArgs) bodyReader() io.Reader { return nil } -func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, gperr.Error) { +func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, error) { if args == nil { args = new(testArgs) } @@ -135,7 +134,7 @@ func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, gpe return newMiddlewaresTest([]*Middleware{mid}, args) } -func newMiddlewaresTest(middlewares []*Middleware, args *testArgs) (*TestResult, gperr.Error) { +func newMiddlewaresTest(middlewares []*Middleware, args *testArgs) (*TestResult, error) { if args == nil { args = new(testArgs) } @@ -160,7 +159,7 @@ func newMiddlewaresTest(middlewares []*Middleware, args *testArgs) (*TestResult, data, err := io.ReadAll(resp.Body) if err != nil { - return nil, gperr.Wrap(err) + return nil, err } return &TestResult{ diff --git a/internal/net/gphttp/middleware/themed.go b/internal/net/gphttp/middleware/themed.go index 67dbd13f..8e0ca4bf 100644 --- a/internal/net/gphttp/middleware/themed.go +++ b/internal/net/gphttp/middleware/themed.go @@ -2,6 +2,7 @@ package middleware import ( "bytes" + "errors" "fmt" "net/http" "os" @@ -66,7 +67,7 @@ func (m *themed) finalize() error { m.m.HTML += buf.String() } if m.CSS != "" && m.Theme != "" { - return gperr.New("css and theme are mutually exclusive") + return errors.New("css and theme are mutually exclusive") } // credit: https://hackcss.egoist.dev if m.Theme != "" { @@ -78,7 +79,7 @@ func (m *themed) finalize() error { case SolarizedDarkTheme: m.m.HTML += wrapStyleTag(solarizedDarkModeCSS) default: - return gperr.New("invalid theme").Subject(string(m.Theme)) + return gperr.PrependSubject(errors.New("invalid theme"), m.Theme) } } if m.CSS != "" { diff --git a/internal/notif/README.md b/internal/notif/README.md index 3571f520..c10bf03c 100644 --- a/internal/notif/README.md +++ b/internal/notif/README.md @@ -304,9 +304,9 @@ The notif package integrates with: ```go var ( - ErrMissingNotifProvider = gperr.New("missing notification provider") - ErrInvalidNotifProviderType = gperr.New("invalid notification provider type") - ErrUnknownNotifProvider = gperr.New("unknown notification provider") + ErrMissingNotifProvider = errors.New("missing notification provider") + ErrInvalidNotifProviderType = errors.New("invalid notification provider type") + ErrUnknownNotifProvider = errors.New("unknown notification provider") ) ``` diff --git a/internal/notif/base.go b/internal/notif/base.go index 87a55366..7ae6d7b3 100644 --- a/internal/notif/base.go +++ b/internal/notif/base.go @@ -1,6 +1,7 @@ package notif import ( + "errors" "io" "net/http" "net/url" @@ -23,13 +24,13 @@ func (e rawError) Error() string { } var ( - ErrMissingToken = gperr.New("token is required") - ErrURLMissingScheme = gperr.New("url missing scheme, expect 'http://' or 'https://'") - ErrUnknownError = gperr.New("unknown error") + ErrMissingToken = errors.New("token is required") + ErrURLMissingScheme = errors.New("url missing scheme, expect 'http://' or 'https://'") + ErrUnknownError = errors.New("unknown error") ) // Validate implements the utils.CustomValidator interface. -func (base *ProviderBase) Validate() gperr.Error { +func (base *ProviderBase) Validate() error { switch base.Format { case "": base.Format = LogFormatMarkdown @@ -48,7 +49,7 @@ func (base *ProviderBase) Validate() gperr.Error { } u, err := url.Parse(base.URL) if err != nil { - return gperr.Wrap(err) + return err } base.URL = u.String() return nil diff --git a/internal/notif/config.go b/internal/notif/config.go index c4838067..eb7a6843 100644 --- a/internal/notif/config.go +++ b/internal/notif/config.go @@ -1,6 +1,9 @@ package notif import ( + "errors" + "strings" + "github.com/yusing/godoxy/internal/serialization" gperr "github.com/yusing/goutils/errs" ) @@ -11,13 +14,13 @@ type NotificationConfig struct { } var ( - ErrMissingNotifProvider = gperr.New("missing notification provider") - ErrInvalidNotifProviderType = gperr.New("invalid notification provider type") - ErrUnknownNotifProvider = gperr.New("unknown notification provider") + ErrMissingNotifProvider = errors.New("missing notification provider") + ErrInvalidNotifProviderType = errors.New("invalid notification provider type") + ErrUnknownNotifProvider = errors.New("unknown notification provider") ) // UnmarshalMap implements MapUnmarshaler. -func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err gperr.Error) { +func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err error) { // extract provider name providerName := m["provider"] switch providerName := providerName.(type) { @@ -41,9 +44,8 @@ func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err gperr.Error) case ProviderNtfy: cfg.Provider = &Ntfy{} default: - return ErrUnknownNotifProvider. - Subject(cfg.ProviderName). - Withf("expect %s or %s", ProviderWebhook, ProviderGotify) + return gperr.PrependSubject(ErrUnknownNotifProvider, cfg.ProviderName). + Withf("expect %s", strings.Join(AvailableProviders, ", ")) } return serialization.MapUnmarshalValidate(m, cfg.Provider) diff --git a/internal/notif/gotify.go b/internal/notif/gotify.go index 140f6fac..15b4a008 100644 --- a/internal/notif/gotify.go +++ b/internal/notif/gotify.go @@ -19,14 +19,15 @@ type ( const gotifyMsgEndpoint = "/message" -func (client *GotifyClient) Validate() gperr.Error { +func (client *GotifyClient) Validate() error { + var errs gperr.Builder if err := client.ProviderBase.Validate(); err != nil { - return err + errs.Add(err) } if client.Token == "" { - return gperr.New("token is required") + errs.Adds("token is required") } - return nil + return errs.Error() } func (client *GotifyClient) GetURL() string { diff --git a/internal/notif/ntfy.go b/internal/notif/ntfy.go index 5f2c3b7e..e45a51f4 100644 --- a/internal/notif/ntfy.go +++ b/internal/notif/ntfy.go @@ -14,20 +14,21 @@ type Ntfy struct { } // Validate implements the utils.CustomValidator interface. -func (n *Ntfy) Validate() gperr.Error { +func (n *Ntfy) Validate() error { + var errs gperr.Builder if err := n.ProviderBase.Validate(); err != nil { - return err + errs.Add(err) } if n.URL == "" { - return gperr.New("url is required") + errs.Adds("url is required") } if n.Topic == "" { - return gperr.New("topic is required") + errs.Adds("topic is required") } - if n.Topic[0] == '/' { - return gperr.New("topic should not start with a slash") + if n.Topic != "" && n.Topic[0] == '/' { + errs.Adds("topic should not start with a slash") } - return nil + return errs.Error() } // GetURL implements Provider. diff --git a/internal/notif/providers.go b/internal/notif/providers.go index ace7235f..68648742 100644 --- a/internal/notif/providers.go +++ b/internal/notif/providers.go @@ -9,7 +9,6 @@ import ( "time" "github.com/yusing/godoxy/internal/serialization" - gperr "github.com/yusing/goutils/errs" ) type ( @@ -27,7 +26,7 @@ type ( fmtError(respBody io.Reader) error } - ProviderCreateFunc func(map[string]any) (Provider, gperr.Error) + ProviderCreateFunc func(map[string]any) (Provider, error) ProviderConfig map[string]any ) @@ -37,6 +36,8 @@ const ( ProviderWebhook = "webhook" ) +var AvailableProviders = []string{ProviderGotify, ProviderNtfy, ProviderWebhook} + func (msg *LogMessage) notify(ctx context.Context, provider Provider) error { body, err := provider.MarshalMessage(msg) if err != nil { diff --git a/internal/notif/webhook.go b/internal/notif/webhook.go index e0f236de..3ab7b3f7 100644 --- a/internal/notif/webhook.go +++ b/internal/notif/webhook.go @@ -2,6 +2,7 @@ package notif import ( _ "embed" + "errors" "io" "net/http" "strings" @@ -26,9 +27,11 @@ var webhookTemplates = map[string]string{ "discord": discordPayload, } -func (webhook *Webhook) Validate() gperr.Error { - if err := webhook.ProviderBase.Validate(); err != nil && !err.Is(ErrMissingToken) { - return err +func (webhook *Webhook) Validate() error { + var errs gperr.Builder + + if err := webhook.ProviderBase.Validate(); err != nil && !errors.Is(err, ErrMissingToken) { + errs.Add(err) } switch webhook.MIMEType { @@ -36,18 +39,18 @@ func (webhook *Webhook) Validate() gperr.Error { webhook.MIMEType = MimeTypeJSON case MimeTypeJSON, MimeTypeForm, MimeTypeText: default: - return gperr.Errorf("invalid mime_type, expect %s", strings.Join([]string{"empty", MimeTypeJSON, MimeTypeForm, MimeTypeText}, ", ")) + errs.Addf("invalid mime_type, expect %s", strings.Join([]string{"empty", MimeTypeJSON, MimeTypeForm, MimeTypeText}, ", ")) } switch webhook.Template { case "": if webhook.MIMEType == MimeTypeJSON { if !validateJSONPayload(webhook.Payload) { - return gperr.New("invalid payload, expect valid JSON") + errs.Adds("invalid payload, expect valid JSON") } } if webhook.Payload == "" { - return gperr.New("invalid payload, expect non-empty") + errs.Adds("invalid payload, expect non-empty") } case "discord": webhook.ColorMode = "dec" @@ -57,7 +60,7 @@ func (webhook *Webhook) Validate() gperr.Error { webhook.Payload = discordPayload } default: - return gperr.New("invalid template, expect empty or 'discord'") + errs.Adds("invalid template, expect empty or 'discord'") } switch webhook.Method { @@ -65,7 +68,7 @@ func (webhook *Webhook) Validate() gperr.Error { webhook.Method = http.MethodPost case http.MethodGet, http.MethodPost, http.MethodPut: default: - return gperr.New("invalid method, expect empty, 'GET', 'POST' or 'PUT'") + errs.Adds("invalid method, expect empty, 'GET', 'POST' or 'PUT'") } switch webhook.ColorMode { @@ -73,10 +76,10 @@ func (webhook *Webhook) Validate() gperr.Error { webhook.ColorMode = "hex" case "hex", "dec": default: - return gperr.New("invalid color_mode, expect empty, 'hex' or 'dec'") + errs.Adds("invalid color_mode, expect empty, 'hex' or 'dec'") } - return nil + return errs.Error() } // GetMethod implements Provider. diff --git a/internal/proxmox/README.md b/internal/proxmox/README.md index 0373ff4d..58fb648e 100644 --- a/internal/proxmox/README.md +++ b/internal/proxmox/README.md @@ -115,7 +115,7 @@ type NodeConfig struct { ```go // Init initializes the Proxmox client. -func (c *Config) Init(ctx context.Context) gperr.Error +func (c *Config) Init(ctx context.Context) error // Client returns the Proxmox client. func (c *Config) Client() *Client @@ -460,7 +460,7 @@ if r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil { node, ok := proxmox.Nodes.Get(node) if !ok { - return gperr.Errorf("proxmox node %s not found", node) + return fmt.Errorf("proxmox node %s not found", node) } // Get container IPs diff --git a/internal/proxmox/config.go b/internal/proxmox/config.go index 54269adf..e92cbea6 100644 --- a/internal/proxmox/config.go +++ b/internal/proxmox/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "fmt" "math" "net/http" "strings" @@ -12,7 +13,6 @@ import ( "github.com/luthermonson/go-proxmox" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/net/gphttp" - gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" ) @@ -44,7 +44,7 @@ func (c *Config) Client() *Client { return c.client } -func (c *Config) Init(ctx context.Context) gperr.Error { +func (c *Config) Init(ctx context.Context) error { var tr *http.Transport if c.NoTLSVerify { // user specified @@ -87,15 +87,15 @@ func (c *Config) Init(ctx context.Context) gperr.Error { if useCredentials { err := c.client.CreateSession(initCtx) if err != nil { - return gperr.New("failed to create session").With(err) + return fmt.Errorf("failed to create session: %w", err) } } if err := c.client.UpdateClusterInfo(initCtx); err != nil { if errors.Is(err, context.DeadlineExceeded) { - return gperr.New("timeout fetching proxmox cluster info") + return fmt.Errorf("timeout fetching proxmox cluster info: %w", err) } - return gperr.New("failed to fetch proxmox cluster info").With(err) + return fmt.Errorf("failed to fetch proxmox cluster info: %w", err) } { diff --git a/internal/proxmox/node.go b/internal/proxmox/node.go index f24d8f14..91e62b16 100644 --- a/internal/proxmox/node.go +++ b/internal/proxmox/node.go @@ -27,7 +27,7 @@ type Node struct { } // Validate implements the serialization.CustomValidator interface. -func (n *NodeConfig) Validate() gperr.Error { +func (n *NodeConfig) Validate() error { var errs gperr.Builder for i, service := range n.Services { if err := checkValidInput(service); err != nil { diff --git a/internal/route/README.md b/internal/route/README.md index b5b2cd7b..b1c5c68a 100644 --- a/internal/route/README.md +++ b/internal/route/README.md @@ -93,8 +93,8 @@ const ( ```go // Validation and lifecycle -func (r *Route) Validate() gperr.Error -func (r *Route) Start(parent task.Parent) gperr.Error +func (r *Route) Validate() error +func (r *Route) Start(parent task.Parent) error func (r *Route) Finish(reason any) func (r *Route) Started() <-chan struct{} @@ -119,8 +119,8 @@ func (r *Route) UseHealthCheck() bool ```mermaid classDiagram class Route { - +Validate() gperr.Error - +Start(parent) gperr.Error + +Validate() error + +Start(parent) error +Finish(reason) +Started() <-chan struct#123;#125; } diff --git a/internal/route/common.go b/internal/route/common.go index bda3cf3e..30b3a130 100644 --- a/internal/route/common.go +++ b/internal/route/common.go @@ -2,16 +2,16 @@ package route import ( "context" + "fmt" entrypoint "github.com/yusing/godoxy/internal/entrypoint/types" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" ) // checkExists checks if the route already exists in the entrypoint. // // Context must be passed from the parent task that carries the entrypoint value. -func checkExists(ctx context.Context, r types.Route) gperr.Error { +func checkExists(ctx context.Context, r types.Route) error { if r.UseLoadBalance() { // skip checking for load balanced routes return nil } @@ -26,7 +26,7 @@ func checkExists(ctx context.Context, r types.Route) gperr.Error { existing, ok = entrypoint.FromCtx(ctx).StreamRoutes().Get(r.Key()) } if ok { - return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName()) + return fmt.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName()) } return nil } diff --git a/internal/route/fileserver.go b/internal/route/fileserver.go index 106a6b65..e3a6cc10 100644 --- a/internal/route/fileserver.go +++ b/internal/route/fileserver.go @@ -1,6 +1,7 @@ package route import ( + "errors" "net/http" "os" "path" @@ -50,12 +51,12 @@ func handler(root string, spa bool, index string) http.Handler { }) } -func NewFileServer(base *Route) (*FileServer, gperr.Error) { +func NewFileServer(base *Route) (*FileServer, error) { s := &FileServer{Route: base} s.Root = filepath.Clean(s.Root) if !path.IsAbs(s.Root) { - return nil, gperr.New("`root` must be an absolute path") + return nil, errors.New("`root` must be an absolute path") } if s.Index == "" { @@ -77,7 +78,7 @@ func NewFileServer(base *Route) (*FileServer, gperr.Error) { } // Start implements task.TaskStarter. -func (s *FileServer) Start(parent task.Parent) gperr.Error { +func (s *FileServer) Start(parent task.Parent) error { s.task = parent.Subtask("fileserver."+s.Name(), false) pathPatterns := s.PathPatterns @@ -109,7 +110,7 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error { s.accessLogger, err = accesslog.NewAccessLogger(s.task, s.AccessLog) if err != nil { s.task.Finish(err) - return gperr.Wrap(err) + return err } } @@ -127,14 +128,14 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error { ep := entrypoint.FromCtx(parent.Context()) if ep == nil { - err := gperr.New("entrypoint not initialized") + err := errors.New("entrypoint not initialized") s.task.Finish(err) return err } if err := ep.StartAddRoute(s); err != nil { s.task.Finish(err) - return gperr.Wrap(err) + return err } return nil } diff --git a/internal/route/provider/README.md b/internal/route/provider/README.md index b378b21d..be206abe 100644 --- a/internal/route/provider/README.md +++ b/internal/route/provider/README.md @@ -39,7 +39,7 @@ type ProviderImpl interface { fmt.Stringer ShortName() string IsExplicitOnly() bool - loadRoutesImpl() (route.Routes, gperr.Error) + loadRoutesImpl() (route.Routes, error) NewWatcher() W.Watcher Logger() *zerolog.Logger } @@ -62,8 +62,8 @@ func NewAgentProvider(cfg *agent.AgentConfig) *Provider ```go func (p *Provider) GetType() provider.Type -func (p *Provider) Start(parent task.Parent) gperr.Error -func (p *Provider) LoadRoutes() gperr.Error +func (p *Provider) Start(parent task.Parent) error +func (p *Provider) LoadRoutes() error func (p *Provider) IterRoutes(yield func(string, types.Route) bool) func (p *Provider) GetRoute(alias string) (types.Route, bool) func (p *Provider) FindService(project, service string) (types.Route, bool) @@ -80,8 +80,8 @@ classDiagram +t provider.Type +routes route.Routes +watcher W.Watcher - +Start(parent) gperr.Error - +LoadRoutes() gperr.Error + +Start(parent) error + +LoadRoutes() error +IterRoutes(yield) } @@ -90,7 +90,7 @@ classDiagram +String() string +ShortName() string +IsExplicitOnly() bool - +loadRoutesImpl() (route.Routes, gperr.Error) + +loadRoutesImpl() (route.Routes, error) +NewWatcher() W.Watcher +Logger() *zerolog.Logger } @@ -99,20 +99,20 @@ classDiagram +name string +dockerCfg types.DockerProviderConfig +ShortName() string - +loadRoutesImpl() (route.Routes, gperr.Error) + +loadRoutesImpl() (route.Routes, error) } class FileProviderImpl { +filename string +ShortName() string - +loadRoutesImpl() (route.Routes, gperr.Error) + +loadRoutesImpl() (route.Routes, error) } class AgentProviderImpl { +*agent.AgentConfig +docker DockerProviderImpl +ShortName() string - +loadRoutesImpl() (route.Routes, gperr.Error) + +loadRoutesImpl() (route.Routes, error) } Provider --> ProviderImpl : wraps diff --git a/internal/route/provider/agent.go b/internal/route/provider/agent.go index 0d35532b..44c7a48f 100644 --- a/internal/route/provider/agent.go +++ b/internal/route/provider/agent.go @@ -5,7 +5,6 @@ import ( "github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/internal/route" "github.com/yusing/godoxy/internal/watcher" - gperr "github.com/yusing/goutils/errs" ) type AgentProvider struct { @@ -25,7 +24,7 @@ func (p *AgentProvider) IsExplicitOnly() bool { return p.docker.IsExplicitOnly() } -func (p *AgentProvider) loadRoutesImpl() (route.Routes, gperr.Error) { +func (p *AgentProvider) loadRoutesImpl() (route.Routes, error) { return p.docker.loadRoutesImpl() } diff --git a/internal/route/provider/docker.go b/internal/route/provider/docker.go index e17445fb..d6415e20 100755 --- a/internal/route/provider/docker.go +++ b/internal/route/provider/docker.go @@ -58,13 +58,13 @@ func (p *DockerProvider) NewWatcher() watcher.Watcher { return watcher.NewDockerWatcher(p.dockerCfg) } -func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) { +func (p *DockerProvider) loadRoutesImpl() (route.Routes, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() containers, err := docker.ListContainers(ctx, p.dockerCfg) if err != nil { - return nil, gperr.Wrap(err) + return nil, err } errs := gperr.NewBuilder("") @@ -74,21 +74,21 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) { container := docker.FromDocker(&c, p.dockerCfg) if container.Errors != nil { - errs.Add(gperr.PrependSubject(container.ContainerName, container.Errors)) + errs.AddSubject(container.Errors, container.ContainerName) continue } if container.IsHostNetworkMode { err := docker.UpdatePorts(ctx, container) if err != nil { - errs.Add(gperr.PrependSubject(container.ContainerName, err)) + errs.AddSubject(err, container.ContainerName) continue } } newEntries, err := p.routesFromContainerLabels(container) if err != nil { - errs.Add(err.Subject(container.ContainerName)) + errs.AddSubject(err, container.ContainerName) } for k, v := range newEntries { if conflict, ok := routes[k]; ok { @@ -97,7 +97,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) { Addf("container %s", container.ContainerName). Addf("conflicting container %s", conflict.Container.ContainerName) if conflict.ShouldExclude() || v.ShouldExclude() { - gperr.LogWarn("skipping conflicting route", err) + log.Warn().Err(err).Msg("skipping conflicting route") } else { errs.Add(err) } @@ -112,7 +112,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) { // Returns a list of proxy entries for a container. // Always non-nil. -func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, gperr.Error) { +func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, error) { if !container.IsExplicit && p.IsExplicitOnly() { return make(route.Routes, 0), nil } @@ -150,7 +150,7 @@ func (p *DockerProvider) routesFromContainerLabels(container *types.Container) ( panic(fmt.Errorf("invalid entry map type %T", entryMapAny)) } if err := yaml.Unmarshal([]byte(yamlStr), &entryMap); err != nil { - errs.Add(gperr.Wrap(err).Subject(alias)) + errs.AddSubject(err, alias) continue } } @@ -185,7 +185,7 @@ func (p *DockerProvider) routesFromContainerLabels(container *types.Container) ( // deserialize map into entry object err := serialization.MapUnmarshalValidate(entryMap, r) if err != nil { - errs.Add(err.Subject(alias)) + errs.AddSubject(err, alias) } else { routes[alias] = r } diff --git a/internal/route/provider/event_handler.go b/internal/route/provider/event_handler.go index 23f45ab3..92126620 100644 --- a/internal/route/provider/event_handler.go +++ b/internal/route/provider/event_handler.go @@ -81,7 +81,7 @@ func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool func (handler *EventHandler) Add(parent task.Parent, route *route.Route) { err := handler.provider.startRoute(parent, route) if err != nil { - handler.errs.Add(err.Subject("add")) + handler.errs.AddSubjectf(err, "add") } } @@ -93,7 +93,7 @@ func (handler *EventHandler) Update(parent task.Parent, oldRoute *route.Route, n oldRoute.FinishAndWait("route update") err := handler.provider.startRoute(parent, newRoute) if err != nil { - handler.errs.Add(err.Subject("update")) + handler.errs.AddSubjectf(err, "update") } } diff --git a/internal/route/provider/file.go b/internal/route/provider/file.go index 82860581..3c78c952 100644 --- a/internal/route/provider/file.go +++ b/internal/route/provider/file.go @@ -12,7 +12,6 @@ import ( "github.com/yusing/godoxy/internal/route" "github.com/yusing/godoxy/internal/serialization" W "github.com/yusing/godoxy/internal/watcher" - gperr "github.com/yusing/goutils/errs" ) type FileProvider struct { @@ -34,7 +33,7 @@ func FileProviderImpl(filename string) (ProviderImpl, error) { return impl, nil } -func removeXPrefix(m map[string]any) gperr.Error { +func removeXPrefix(m map[string]any) error { for alias := range m { if strings.HasPrefix(alias, "x-") { delete(m, alias) @@ -43,12 +42,12 @@ func removeXPrefix(m map[string]any) gperr.Error { return nil } -func validate(data []byte) (routes route.Routes, err gperr.Error) { +func validate(data []byte) (routes route.Routes, err error) { err = serialization.UnmarshalValidate(data, &routes, yaml.Unmarshal, removeXPrefix) return routes, err } -func Validate(data []byte) (err gperr.Error) { +func Validate(data []byte) (err error) { _, err = validate(data) return err } @@ -69,16 +68,16 @@ func (p *FileProvider) Logger() *zerolog.Logger { return &p.l } -func (p *FileProvider) loadRoutesImpl() (route.Routes, gperr.Error) { +func (p *FileProvider) loadRoutesImpl() (route.Routes, error) { data, err := os.ReadFile(p.path) if err != nil { - return nil, gperr.Wrap(err) + return nil, err } routes, err := validate(data) if err != nil && len(routes) == 0 { - return nil, gperr.Wrap(err) + return nil, err } - return routes, gperr.Wrap(err) + return routes, err } func (p *FileProvider) NewWatcher() W.Watcher { diff --git a/internal/route/provider/provider.go b/internal/route/provider/provider.go index de32fe8a..3af59dab 100644 --- a/internal/route/provider/provider.go +++ b/internal/route/provider/provider.go @@ -34,7 +34,7 @@ type ( fmt.Stringer ShortName() string IsExplicitOnly() bool - loadRoutesImpl() (route.Routes, gperr.Error) + loadRoutesImpl() (route.Routes, error) NewWatcher() W.Watcher Logger() *zerolog.Logger } @@ -96,7 +96,7 @@ func (p *Provider) MarshalText() ([]byte, error) { } // Start implements task.TaskStarter. -func (p *Provider) Start(parent task.Parent) gperr.Error { +func (p *Provider) Start(parent task.Parent) error { errs := gperr.NewGroup("routes error") t := parent.Subtask("provider."+p.String(), false) @@ -124,8 +124,8 @@ func (p *Provider) Start(parent task.Parent) gperr.Error { handler.Handle(t, events) handler.Log() }, - func(err gperr.Error) { - gperr.LogError("event error", err, p.Logger()) + func(err error) { + p.Logger().Err(err).Msg("event error") }, ) eventQueue.Start(p.watcher.Events(t.Context())) @@ -136,7 +136,7 @@ func (p *Provider) Start(parent task.Parent) gperr.Error { return nil } -func (p *Provider) LoadRoutes() (err gperr.Error) { +func (p *Provider) LoadRoutes() (err error) { p.routes, err = p.loadRoutes() return err } @@ -188,7 +188,7 @@ func (p *Provider) GetRoute(alias string) (types.Route, bool) { return r.Impl(), true } -func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) { +func (p *Provider) loadRoutes() (routes route.Routes, err error) { routes, err = p.loadRoutesImpl() if err != nil && len(routes) == 0 { return route.Routes{}, err @@ -201,7 +201,7 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) { r.Alias = alias r.SetProvider(p) if err := r.Validate(); err != nil { - errs.Add(err.Subject(alias)) + errs.AddSubject(err, alias) delete(routes, alias) continue } @@ -210,11 +210,11 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) { return routes, errs.Error() } -func (p *Provider) startRoute(parent task.Parent, r *route.Route) gperr.Error { +func (p *Provider) startRoute(parent task.Parent, r *route.Route) error { err := r.Start(parent) if err != nil { p.lockDeleteRoute(r.Alias) - return err.Subject(r.Alias) + return gperr.PrependSubject(err, r.Alias) } p.lockAddRoute(r) diff --git a/internal/route/reverse_proxy.go b/internal/route/reverse_proxy.go index 32c54c23..0fc29dcd 100755 --- a/internal/route/reverse_proxy.go +++ b/internal/route/reverse_proxy.go @@ -1,9 +1,11 @@ package route import ( + "errors" "net/http" "sync" + "github.com/rs/zerolog/log" "github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agentproxy" entrypoint "github.com/yusing/godoxy/internal/entrypoint/types" @@ -16,7 +18,6 @@ import ( nettypes "github.com/yusing/godoxy/internal/net/types" route "github.com/yusing/godoxy/internal/route/types" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/http/reverseproxy" "github.com/yusing/goutils/task" "github.com/yusing/goutils/version" @@ -34,7 +35,7 @@ var _ types.ReverseProxyRoute = (*ReveseProxyRoute)(nil) // var globalMux = http.NewServeMux() // TODO: support regex subdomain matching. -func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) { +func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, error) { httpConfig := base.HTTPConfig proxyURL := base.ProxyURL @@ -123,7 +124,7 @@ func (r *ReveseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy { } // Start implements task.TaskStarter. -func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { +func (r *ReveseProxyRoute) Start(parent task.Parent) error { r.task = parent.Subtask("http."+r.Name(), false) switch { @@ -131,7 +132,7 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { waker, err := idlewatcher.NewWatcher(parent, r, r.IdlewatcherConfig()) if err != nil { r.task.Finish(err) - return gperr.Wrap(err) + return err } r.handler = waker r.HealthMon = waker @@ -148,7 +149,7 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { r.rp.AccessLogger, err = accesslog.NewAccessLogger(r.task, r.AccessLog) if err != nil { r.task.Finish(err) - return gperr.Wrap(err) + return err } } @@ -158,14 +159,14 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { if r.HealthMon != nil { if err := r.HealthMon.Start(r.task); err != nil { - gperr.LogWarn("health monitor error", err, &r.rp.Logger) + log.Warn().Err(err).Msg("health monitor error") r.HealthMon = nil } } ep := entrypoint.FromCtx(parent.Context()) if ep == nil { - err := gperr.New("entrypoint not initialized") + err := errors.New("entrypoint not initialized") r.task.Finish(err) return err } @@ -173,12 +174,12 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { if r.UseLoadBalance() { if err := r.addToLoadBalancer(parent, ep); err != nil { r.task.Finish(err) - return gperr.Wrap(err) + return err } } else { if err := ep.StartAddRoute(r); err != nil { r.task.Finish(err) - return gperr.Wrap(err) + return err } } return nil diff --git a/internal/route/route.go b/internal/route/route.go index f5bc5965..0b740431 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -108,17 +108,17 @@ type ( ) type lockedError struct { - err gperr.Error + err error lock sync.Mutex } -func (le *lockedError) Get() gperr.Error { +func (le *lockedError) Get() error { le.lock.Lock() defer le.lock.Unlock() return le.err } -func (le *lockedError) Set(err gperr.Error) { +func (le *lockedError) Set(err error) { le.lock.Lock() defer le.lock.Unlock() le.err = err @@ -131,7 +131,7 @@ func (r Routes) Contains(alias string) bool { return ok } -func (r *Route) Validate() gperr.Error { +func (r *Route) Validate() error { // wait for alias to be set if r.Alias == "" { return nil @@ -150,13 +150,13 @@ func (r *Route) Validate() gperr.Error { return r.valErr.Get() } -func (r *Route) validate() gperr.Error { +func (r *Route) validate() error { // if strings.HasPrefix(r.Alias, "godoxy") { // log.Debug().Any("route", r).Msg("validating route") // } if r.Agent != "" { if r.Container != nil { - return gperr.Errorf("specifying agent is not allowed for docker container routes") + return errors.New("specifying agent is not allowed for docker container routes") } var ok bool // by agent address @@ -165,7 +165,7 @@ func (r *Route) validate() gperr.Error { // fallback to get agent by name r.agent, ok = agentpool.GetAgent(r.Agent) if !ok { - return gperr.Errorf("agent %s not found", r.Agent) + return fmt.Errorf("agent %s not found", r.Agent) } } } @@ -258,7 +258,7 @@ func (r *Route) validate() gperr.Error { switch r.Port.Proxy { case common.ProxyHTTPPort, common.ProxyHTTPSPort, common.APIHTTPPort: if r.Scheme.IsReverseProxy() || r.Scheme == route.SchemeTCP { - return gperr.Errorf("localhost:%d is reserved for godoxy", r.Port.Proxy) + return fmt.Errorf("localhost:%d is reserved for godoxy", r.Port.Proxy) } } } @@ -269,9 +269,6 @@ func (r *Route) validate() gperr.Error { errs.Add(err) } - var impl types.Route - var err gperr.Error - if r.ShouldExclude() { r.ProxyURL = gperr.Collect(&errs, nettypes.ParseURL, fmt.Sprintf("%s://%s", r.Scheme, net.JoinHostPort(r.Host, strconv.Itoa(r.Port.Proxy)))) } else { @@ -318,6 +315,8 @@ func (r *Route) validate() gperr.Error { return errs.Error() } + var impl types.Route + var err error switch r.Scheme { case route.SchemeFileServer: impl, err = NewFileServer(r) @@ -479,16 +478,16 @@ func (r *Route) Task() *task.Task { return r.task } -func (r *Route) Start(parent task.Parent) gperr.Error { +func (r *Route) Start(parent task.Parent) error { r.onceStart.Do(func() { r.startErr.Set(r.start(parent)) }) return r.startErr.Get() } -func (r *Route) start(parent task.Parent) gperr.Error { +func (r *Route) start(parent task.Parent) error { if r.impl == nil { // should not happen - return gperr.New("route not initialized") + return errors.New("route not initialized") } defer close(r.started) @@ -511,7 +510,7 @@ func (r *Route) start(parent task.Parent) gperr.Error { } else { ep := entrypoint.FromCtx(parent.Context()) if ep == nil { - return gperr.New("entrypoint not initialized") + return errors.New("entrypoint not initialized") } r.task = parent.Subtask("excluded."+r.Name(), false) diff --git a/internal/route/rules/do.go b/internal/route/rules/do.go index 26c65564..f1ed1cbd 100644 --- a/internal/route/rules/do.go +++ b/internal/route/rules/do.go @@ -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) diff --git a/internal/route/rules/do_log_test.go b/internal/route/rules/do_log_test.go index e7741ab1..352ea752 100644 --- a/internal/route/rules/do_log_test.go +++ b/internal/route/rules/do_log_test.go @@ -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 } diff --git a/internal/route/rules/do_set.go b/internal/route/rules/do_set.go index 97e7a8ef..025f9f1c 100644 --- a/internal/route/rules/do_set.go +++ b/internal/route/rules/do_set.go @@ -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 } diff --git a/internal/route/rules/error_format_test.go b/internal/route/rules/error_format_test.go index 9a2846b6..9d086880 100644 --- a/internal/route/rules/error_format_test.go +++ b/internal/route/rules/error_format_test.go @@ -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") } diff --git a/internal/route/rules/help.go b/internal/route/rules/help.go index de1bf00c..52b84786 100644 --- a/internal/route/rules/help.go +++ b/internal/route/rules/help.go @@ -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)) diff --git a/internal/route/rules/http_flow_test.go b/internal/route/rules/http_flow_test.go index b8321e85..cdf13261 100644 --- a/internal/route/rules/http_flow_test.go +++ b/internal/route/rules/http_flow_test.go @@ -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 } diff --git a/internal/route/rules/io.go b/internal/route/rules/io.go index 67df0ac3..6ce0e53c 100644 --- a/internal/route/rules/io.go +++ b/internal/route/rules/io.go @@ -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 diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 4471c656..ed0293f1 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -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) diff --git a/internal/route/rules/parser.go b/internal/route/rules/parser.go index 149da13b..83265323 100644 --- a/internal/route/rules/parser.go +++ b/internal/route/rules/parser.go @@ -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 diff --git a/internal/route/rules/presets/embed.go b/internal/route/rules/presets/embed.go index 77e4d7ba..4a0c9238 100644 --- a/internal/route/rules/presets/embed.go +++ b/internal/route/rules/presets/embed.go @@ -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 diff --git a/internal/route/rules/rules.go b/internal/route/rules/rules.go index ebaf5fab..9e6d52cf 100644 --- a/internal/route/rules/rules.go +++ b/internal/route/rules/rules.go @@ -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 { diff --git a/internal/route/rules/validate.go b/internal/route/rules/validate.go index 14c71a3f..dde408b9 100644 --- a/internal/route/rules/validate.go +++ b/internal/route/rules/validate.go @@ -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 -// } diff --git a/internal/route/stream.go b/internal/route/stream.go index 3b05cbe2..00282b72 100755 --- a/internal/route/stream.go +++ b/internal/route/stream.go @@ -2,17 +2,18 @@ package route import ( "context" + "errors" "fmt" "net" "strings" + "github.com/rs/zerolog/log" entrypoint "github.com/yusing/godoxy/internal/entrypoint/types" "github.com/yusing/godoxy/internal/health/monitor" "github.com/yusing/godoxy/internal/idlewatcher" nettypes "github.com/yusing/godoxy/internal/net/types" "github.com/yusing/godoxy/internal/route/stream" "github.com/yusing/godoxy/internal/types" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" ) @@ -24,7 +25,7 @@ type StreamRoute struct { var _ types.StreamRoute = (*StreamRoute)(nil) -func NewStreamRoute(base *Route) (types.Route, gperr.Error) { +func NewStreamRoute(base *Route) (types.Route, error) { // TODO: support non-coherent scheme return &StreamRoute{Route: base}, nil } @@ -34,14 +35,14 @@ func (r *StreamRoute) Stream() nettypes.Stream { } // Start implements task.TaskStarter. -func (r *StreamRoute) Start(parent task.Parent) gperr.Error { +func (r *StreamRoute) Start(parent task.Parent) error { if r.LisURL == nil { - return gperr.Errorf("listen URL is not set") + return errors.New("listen URL is not set") } stream, err := r.initStream() if err != nil { - return gperr.Wrap(err) + return err } r.stream = stream @@ -52,7 +53,7 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error { waker, err := idlewatcher.NewWatcher(parent, r, r.IdlewatcherConfig()) if err != nil { r.task.Finish(err) - return gperr.Wrap(err, "idlewatcher error") + return fmt.Errorf("idlewatcher error: %w", err) } r.stream = waker r.HealthMon = waker @@ -62,20 +63,20 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error { if r.HealthMon != nil { if err := r.HealthMon.Start(r.task); err != nil { - gperr.LogWarn("health monitor error", err) + log.Warn().Err(err).Msg("health monitor error") r.HealthMon = nil } } ep := entrypoint.FromCtx(parent.Context()) if ep == nil { - err := gperr.New("entrypoint not initialized") + err := errors.New("entrypoint not initialized") r.task.Finish(err) return err } if err := ep.StartAddRoute(r); err != nil { r.task.Finish(err) - return gperr.Wrap(err) + return err } return nil } diff --git a/internal/route/types/http_config.go b/internal/route/types/http_config.go index 03a65dde..3ba5f769 100644 --- a/internal/route/types/http_config.go +++ b/internal/route/types/http_config.go @@ -3,6 +3,7 @@ package route import ( "crypto/tls" "crypto/x509" + "errors" "net/url" "os" "strings" @@ -25,7 +26,7 @@ type HTTPConfig struct { } // BuildTLSConfig creates a TLS configuration based on the HTTP config options. -func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Error) { +func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, error) { tlsConfig := &tls.Config{} // Handle InsecureSkipVerify (legacy NoTLSVerify option) @@ -54,15 +55,12 @@ func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Er if cfg.SSLTrustedCertificate != "" { caCertData, err := os.ReadFile(cfg.SSLTrustedCertificate) if err != nil { - return nil, gperr.New("failed to read trusted certificate file"). - Subject(cfg.SSLTrustedCertificate). - With(err) + return nil, gperr.PrependSubject(err, cfg.SSLTrustedCertificate) } caCertPool := x509.NewCertPool() if !caCertPool.AppendCertsFromPEM(caCertData) { - return nil, gperr.New("failed to parse trusted certificates"). - Subject(cfg.SSLTrustedCertificate) + return nil, gperr.PrependSubject(errors.New("failed to parse trusted certificates"), cfg.SSLTrustedCertificate) } tlsConfig.RootCAs = caCertPool } @@ -70,16 +68,16 @@ func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Er // Handle ssl_certificate and ssl_certificate_key (client certificates) if cfg.SSLCertificate != "" { if cfg.SSLCertificateKey == "" { - return nil, gperr.New("ssl_certificate_key is required when ssl_certificate is specified") + return nil, errors.New("ssl_certificate_key is required when ssl_certificate is specified") } clientCert, err := tls.LoadX509KeyPair(cfg.SSLCertificate, cfg.SSLCertificateKey) if err != nil { - return nil, gperr.New("failed to load client certificate"). - Subject(cfg.SSLCertificate). - With(err) + return nil, gperr.PrependSubject(err, cfg.SSLCertificate) } tlsConfig.Certificates = []tls.Certificate{clientCert} + } else if cfg.SSLCertificateKey != "" { + return nil, errors.New("ssl_certificate is required when ssl_certificate_key is specified") } // Handle ssl_protocols (TLS versions) diff --git a/internal/route/types/port.go b/internal/route/types/port.go index 769f9831..eb4382ed 100644 --- a/internal/route/types/port.go +++ b/internal/route/types/port.go @@ -1,6 +1,7 @@ package route import ( + "errors" "strconv" gperr "github.com/yusing/goutils/errs" @@ -13,8 +14,8 @@ type Port struct { } // @name Port var ( - ErrInvalidPortSyntax = gperr.New("invalid port syntax, expect [listening_port:]target_port") - ErrPortOutOfRange = gperr.New("port out of range") + ErrInvalidPortSyntax = errors.New("invalid port syntax, expect [listening_port:]target_port") + ErrPortOutOfRange = errors.New("port out of range") ) // Parse implements strutils.Parser. @@ -30,7 +31,7 @@ func (p *Port) Parse(v string) (err error) { p.Proxy, err2 = strconv.Atoi(parts[1]) err = gperr.Join(err, err2) default: - return ErrInvalidPortSyntax.Subject(v) + return gperr.PrependSubject(ErrInvalidPortSyntax, v) } if err != nil { @@ -38,11 +39,11 @@ func (p *Port) Parse(v string) (err error) { } if p.Listening < MinPort || p.Listening > MaxPort { - return ErrPortOutOfRange.Subjectf("%d", p.Listening) + return gperr.PrependSubject(ErrPortOutOfRange, strconv.Itoa(p.Listening)) } if p.Proxy < MinPort || p.Proxy > MaxPort { - return ErrPortOutOfRange.Subjectf("%d", p.Proxy) + return gperr.PrependSubject(ErrPortOutOfRange, strconv.Itoa(p.Proxy)) } return nil diff --git a/internal/route/types/scheme.go b/internal/route/types/scheme.go index b7044ab3..dc6af468 100644 --- a/internal/route/types/scheme.go +++ b/internal/route/types/scheme.go @@ -1,6 +1,7 @@ package route import ( + "errors" "strconv" "github.com/bytedance/sonic" @@ -9,7 +10,7 @@ import ( type Scheme uint8 -var ErrInvalidScheme = gperr.New("invalid scheme") +var ErrInvalidScheme = errors.New("invalid scheme") const ( SchemeHTTP Scheme = 1 << iota @@ -79,7 +80,7 @@ func (s *Scheme) Parse(v string) error { case schemeStrFileServer: *s = SchemeFileServer default: - return ErrInvalidScheme.Subject(v) + return gperr.PrependSubject(ErrInvalidScheme, v) } return nil } diff --git a/internal/serialization/README.md b/internal/serialization/README.md index 89cedf34..45aa3c16 100644 --- a/internal/serialization/README.md +++ b/internal/serialization/README.md @@ -43,12 +43,12 @@ type SerializedObject = map[string]any ```go // For custom map unmarshaling logic type MapUnmarshaller interface { - UnmarshalMap(m map[string]any) gperr.Error + UnmarshalMap(m map[string]any) error } // For custom validation logic type CustomValidator interface { - Validate() gperr.Error + Validate() error } ``` @@ -56,16 +56,16 @@ type CustomValidator interface { ```go // Generic unmarshal with pluggable format handler -func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) gperr.Error +func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) error // Read from io.Reader with format decoder -func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) gperr.Error +func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) error // Direct map deserialization -func MapUnmarshalValidate(src SerializedObject, dst any) gperr.Error +func MapUnmarshalValidate(src SerializedObject, dst any) error // To xsync.Map with pluggable format handler -func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], gperr.Error) +func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], error) ``` ### File I/O Functions @@ -82,23 +82,23 @@ func LoadFileIfExist[T any](path string, dst *T, unmarshaler unmarshalFunc) erro ```go // Convert any value to target reflect.Value -func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error +func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) error // String to target type conversion -func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) +func ConvertString(src string, dst reflect.Value) (convertible bool, convErr error) ``` ### Validation Functions ```go // Validate using struct tags -func ValidateWithFieldTags(s any) gperr.Error +func ValidateWithFieldTags(s any) error // Register custom validator func MustRegisterValidation(tag string, fn validator.Func) // Validate using CustomValidator interface -func ValidateWithCustomValidator(v reflect.Value) gperr.Error +func ValidateWithCustomValidator(v reflect.Value) error // Get underlying validator func Validator() *validator.Validate @@ -301,9 +301,9 @@ type Config struct { URL string `json:"url" validate:"required"` } -func (c *Config) Validate() gperr.Error { +func (c *Config) Validate() error { if !strings.HasPrefix(c.URL, "https://") { - return gperr.New("url must use https").Subject("url") + return errors.New("url must use https") } return nil } diff --git a/internal/serialization/serialization.go b/internal/serialization/serialization.go index 920abbb9..14781855 100644 --- a/internal/serialization/serialization.go +++ b/internal/serialization/serialization.go @@ -2,6 +2,7 @@ package serialization import ( "errors" + "fmt" "io" "os" "reflect" @@ -40,15 +41,15 @@ func init() { } type MapUnmarshaller interface { - UnmarshalMap(m map[string]any) gperr.Error + UnmarshalMap(m map[string]any) error } var ( - ErrInvalidType = gperr.New("invalid type") - ErrNilValue = gperr.New("nil") - ErrUnsettable = gperr.New("unsettable") - ErrUnsupportedConversion = gperr.New("unsupported conversion") - ErrUnknownField = gperr.New("unknown field") + ErrInvalidType = errors.New("invalid type") + ErrNilValue = errors.New("nil") + ErrUnsettable = errors.New("unsettable") + ErrUnsupportedConversion = errors.New("unsupported conversion") + ErrUnknownField = errors.New("unknown field") ) var ( @@ -90,7 +91,7 @@ func initPtr(dst reflect.Value) { // // It collects all validation errors and returns them as a single error. // Field names in errors are prefixed with their namespace (e.g., "User.Email"). -func ValidateWithFieldTags(s any) gperr.Error { +func ValidateWithFieldTags(s any) error { var errs gperr.Builder err := validate.Struct(s) var valErrs validator.ValidationErrors @@ -103,15 +104,14 @@ func ValidateWithFieldTags(s any) gperr.Error { if detail != "required" { detail = "require " + strconv.Quote(detail) } - errs.Add(ErrValidationError. - Subject(e.Namespace()). + errs.Add(gperr.PrependSubject(ErrValidationError, e.Namespace()). Withf(detail)) } } return errs.Error() } -func dive(dst reflect.Value) (v reflect.Value, t reflect.Type, err gperr.Error) { +func dive(dst reflect.Value) (v reflect.Value, t reflect.Type) { dstT := dst.Type() for { switch dstT.Kind() { @@ -119,7 +119,7 @@ func dive(dst reflect.Value) (v reflect.Value, t reflect.Type, err gperr.Error) dst = dst.Elem() dstT = dstT.Elem() default: - return dst, dstT, nil + return dst, dstT } } } @@ -276,32 +276,26 @@ func initTypeKeyFieldIndexesMap(t reflect.Type) typeInfo { // If the target value is a map[string]any the SerializedObject will be deserialized into the map. // // The function returns an error if the target value is not a struct or a map[string]any, or if there is an error during deserialization. -func MapUnmarshalValidate(src SerializedObject, dst any) (err gperr.Error) { +func MapUnmarshalValidate(src SerializedObject, dst any) error { return mapUnmarshalValidate(src, reflect.ValueOf(dst), true) } -func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidateTag bool) (err gperr.Error) { +func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidateTag bool) (err error) { dstT := dstV.Type() if src != nil && dstT.Implements(mapUnmarshalerType) { - dstV, _, err = dive(dstV) - if err != nil { - return err - } + dstV, _ = dive(dstV) return dstV.Addr().Interface().(MapUnmarshaller).UnmarshalMap(src) } - dstV, dstT, err = dive(dstV) - if err != nil { - return err - } + dstV, dstT = dive(dstV) if src == nil { if dstV.CanSet() { dstV.SetZero() return nil } - return gperr.Errorf("deserialize: src is %w and dst is not settable", ErrNilValue) + return fmt.Errorf("deserialize: src is %w and dst is not settable", ErrNilValue) } // convert data fields to lower no-snake @@ -317,10 +311,10 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat if field, ok := info.getField(dstV, k); ok { err := Convert(reflect.ValueOf(v), field, checkValidateTag) if err != nil { - errs.Add(err.Subject(k)) + errs.AddSubject(err, k) } } else { - errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMeanField(k, info.fieldNames))) + errs.Add(gperr.PrependSubject(ErrUnknownField, k).With(gperr.DoYouMeanField(k, info.fieldNames))) } } if info.hasValidateTag && checkValidateTag { @@ -333,23 +327,23 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat case reflect.Map: if dstV.IsNil() { if !dstV.CanSet() { - return gperr.Errorf("dive: dst is %w and is not settable", ErrNilValue) + return fmt.Errorf("dive: dst is %w and is not settable", ErrNilValue) } gi.ReflectInitMap(dstV, len(src)) } if dstT.Key().Kind() != reflect.String { - return gperr.Errorf("deserialize: %w for map of non string keys (map of %s)", ErrUnsupportedConversion, dstT.Elem().String()) + return fmt.Errorf("deserialize: %w for map of non string keys (map of %s)", ErrUnsupportedConversion, dstT.Elem().String()) } // ?: should we clear the map? for k, v := range src { elem := gi.ReflectStrMapAssign(dstV, k) err := Convert(reflect.ValueOf(v), elem, true) if err != nil { - errs.Add(err.Subject(k)) + errs.AddSubject(err, k) continue } if err := ValidateWithCustomValidator(elem); err != nil { - errs.Add(err.Subject(k)) + errs.AddSubject(err, k) } } if err := ValidateWithCustomValidator(dstV); err != nil { @@ -357,7 +351,7 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat } return errs.Error() default: - return ErrUnsupportedConversion.Subject("mapping to " + dstT.String() + " ") + return fmt.Errorf("deserialize: %w for mapping to %s", ErrUnsupportedConversion, dstT) } } @@ -373,14 +367,14 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat // // Returns: // - error: the error occurred during conversion, or nil if no error occurred. -func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error { +func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) error { if !dst.IsValid() { - return gperr.Errorf("convert: dst is %w", ErrNilValue) + return fmt.Errorf("convert: dst is %w", ErrNilValue) } if (src.Kind() == reflect.Pointer && src.IsNil()) || !src.IsValid() { if !dst.CanSet() { - return gperr.Errorf("convert: src is %w", ErrNilValue) + return fmt.Errorf("convert: src is %w", ErrNilValue) } dst.SetZero() return nil @@ -388,7 +382,7 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. if src.IsZero() { if !dst.CanSet() { - return gperr.Errorf("convert: src is %w", ErrNilValue) + return fmt.Errorf("convert: src is %w", ErrNilValue) } switch dst.Kind() { case reflect.Pointer: @@ -410,7 +404,7 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. if dst.Kind() == reflect.Pointer { if dst.IsNil() { if !dst.CanSet() { - return ErrUnsettable.Subject(dstT.String()) + return fmt.Errorf("convert: dst is %w", ErrUnsettable) } initPtr(dst) } @@ -423,13 +417,13 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. switch { case srcT == dstT, srcT.AssignableTo(dstT): if !dst.CanSet() { - return ErrUnsettable.Subject(dstT.String()) + return fmt.Errorf("convert: dst is %w", ErrUnsettable) } dst.Set(src) return nil case srcKind == reflect.String: if !dst.CanSet() { - return ErrUnsettable.Subject(dstT.String()) + return fmt.Errorf("convert: dst is %w", ErrUnsettable) } if convertible, err := ConvertString(src.String(), dst); convertible { return err @@ -451,14 +445,14 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. } obj, ok := src.Interface().(SerializedObject) if !ok { - return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String()) + return fmt.Errorf("convert: %w for %s to %s", ErrUnsupportedConversion, dstT, srcT) } return mapUnmarshalValidate(obj, dst.Addr(), checkValidateTag) case srcKind == reflect.Slice: // slice to slice return ConvertSlice(src, dst, checkValidateTag) } - return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT) + return fmt.Errorf("convert: %w for %s to %s", ErrUnsupportedConversion, srcT, dstT) } // ConvertSlice converts a source slice to a destination slice. @@ -468,17 +462,17 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr. // - The destination slice is initialized with the source length. // - On error, the destination slice is truncated to the number of // successfully converted elements. -func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error { +func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) error { if dst.Kind() == reflect.Pointer { if dst.IsNil() && !dst.CanSet() { - return ErrNilValue + return fmt.Errorf("convert: dst is %w", ErrNilValue) } initPtr(dst) dst = dst.Elem() } if !dst.CanSet() { - return ErrUnsettable.Subject(dst.Type().String()) + return fmt.Errorf("convert: dst is %w", ErrUnsettable) } if src.Kind() != reflect.Slice { @@ -491,7 +485,7 @@ func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) g return nil } if dst.Kind() != reflect.Slice { - return ErrUnsupportedConversion.Subjectf("%s to %s", dst.Type(), src.Type()) + return fmt.Errorf("convert: %w for %s to %s", ErrUnsupportedConversion, dst.Type(), src.Type()) } var sliceErrs gperr.Builder @@ -500,7 +494,7 @@ func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) g for j := range srcLen { err := Convert(src.Index(j), dst.Index(numValid), checkValidateTag) if err != nil { - sliceErrs.Add(err.Subjectf("[%d]", j)) + sliceErrs.AddSubjectf(err, "[%d]", j) continue } numValid++ @@ -526,7 +520,7 @@ func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) g // - If the destination implements the Parser interface, it is used for conversion. // - Returns true if conversion was handled (even with error), false if // conversion is unsupported. -func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) { +func ConvertString(src string, dst reflect.Value) (convertible bool, convErr error) { convertible = true dstT := dst.Type() if dst.Kind() == reflect.Pointer { @@ -555,14 +549,14 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe // check if (*T).Convertor is implemented if addr := dst.Addr(); addr.Type().Implements(reflect.TypeFor[strutils.Parser]()) { parser := addr.Interface().(strutils.Parser) - return true, gperr.Wrap(parser.Parse(src)) + return true, parser.Parse(src) } switch dstT { case reflect.TypeFor[time.Duration](): d, err := time.ParseDuration(src) if err != nil { - return true, gperr.Wrap(err) + return true, err } gi.ReflectValueSet(dst, d) return true, nil @@ -572,7 +566,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe if gi.ReflectIsNumeric(dst) || dst.Kind() == reflect.Bool { err := gi.ReflectStrToNumBool(dst, src) if err != nil { - return true, gperr.Wrap(err) + return true, err } return true, nil } @@ -602,14 +596,14 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe sl := []any{} err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &sl) if err != nil { - return true, gperr.Wrap(err) + return true, err } return true, ConvertSlice(reflect.ValueOf(sl), dst, true) case reflect.Map, reflect.Struct: rawMap := SerializedObject{} err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &rawMap) if err != nil { - return true, gperr.Wrap(err) + return true, err } return true, mapUnmarshalValidate(rawMap, dst, true) default: @@ -619,7 +613,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY} -func substituteEnv(data []byte) ([]byte, gperr.Error) { +func substituteEnv(data []byte) ([]byte, error) { envError := gperr.NewBuilder("env substitution error") data = envRegex.ReplaceAllFunc(data, func(match []byte) []byte { varName := string(match[2 : len(match)-1]) @@ -643,7 +637,7 @@ type ( newDecoderFunc func(r io.Reader) interface { Decode(v any) error } - interceptFunc func(m map[string]any) gperr.Error + interceptFunc func(m map[string]any) error ) // UnmarshalValidate unmarshals data into a map, applies optional intercept @@ -651,7 +645,7 @@ type ( // - Environment variables in the data are substituted using ${VAR} syntax. // - The unmarshaler function converts data to a map[string]any. // - Intercept functions can modify or validate the map before unmarshaling. -func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) gperr.Error { +func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) error { data, err := substituteEnv(data) if err != nil { return err @@ -659,7 +653,7 @@ func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, m := make(map[string]any) if err := unmarshaler(data, &m); err != nil { - return gperr.Wrap(err) + return err } for _, intercept := range interceptFns { if err := intercept(m); err != nil { @@ -674,10 +668,10 @@ func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, // - Environment variables are substituted during reading using ${VAR} syntax. // - The newDecoder function creates a decoder for the reader (e.g., // json.NewDecoder). -func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) gperr.Error { +func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) error { m := make(map[string]any) if err := newDecoder(NewSubstituteEnvReader(reader)).Decode(&m); err != nil { - return gperr.Wrap(err) + return err } for _, intercept := range interceptFns { if err := intercept(m); err != nil { @@ -692,7 +686,7 @@ func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newD // - The unmarshaler function converts data to a map[string]any. // - Intercept functions can modify or validate the map before unmarshaling. // - Returns a thread-safe concurrent map with the unmarshaled values. -func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], gperr.Error) { +func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], error) { data, err := substituteEnv(data) if err != nil { return nil, err @@ -700,7 +694,7 @@ func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, inter m := make(map[string]any) if err := unmarshaler(data, &m); err != nil { - return nil, gperr.Wrap(err) + return nil, err } for _, intercept := range interceptFns { if err := intercept(m); err != nil { diff --git a/internal/serialization/serialization_benchmark_test.go b/internal/serialization/serialization_benchmark_test.go index 4fb881df..45357797 100644 --- a/internal/serialization/serialization_benchmark_test.go +++ b/internal/serialization/serialization_benchmark_test.go @@ -42,7 +42,7 @@ func BenchmarkDeserialize(b *testing.B) { dst := complexStruct{} err := MapUnmarshalValidate(src, &dst) if err != nil { - b.Fatal(string(err.Plain())) + b.Fatal(err.Error()) } } } diff --git a/internal/serialization/validation.go b/internal/serialization/validation.go index bebd9370..7582bdaa 100644 --- a/internal/serialization/validation.go +++ b/internal/serialization/validation.go @@ -1,15 +1,15 @@ package serialization import ( + "errors" "reflect" "github.com/go-playground/validator/v10" - gperr "github.com/yusing/goutils/errs" ) var validate = validator.New() -var ErrValidationError = gperr.New("validation error") +var ErrValidationError = errors.New("validation error") func Validator() *validator.Validate { return validate @@ -23,12 +23,12 @@ func MustRegisterValidation(tag string, fn validator.Func) { } type CustomValidator interface { - Validate() gperr.Error + Validate() error } var validatorType = reflect.TypeFor[CustomValidator]() -func ValidateWithCustomValidator(v reflect.Value) gperr.Error { +func ValidateWithCustomValidator(v reflect.Value) error { vt := v.Type() if v.Kind() == reflect.Pointer { elemType := vt.Elem() diff --git a/internal/types/docker_provider_config.go b/internal/types/docker_provider_config.go index e5117809..99410c4b 100644 --- a/internal/types/docker_provider_config.go +++ b/internal/types/docker_provider_config.go @@ -59,7 +59,7 @@ func (cfg *DockerProviderConfig) Parse(value string) error { return nil } -func (cfg *DockerProviderConfig) UnmarshalMap(m map[string]any) gperr.Error { +func (cfg *DockerProviderConfig) UnmarshalMap(m map[string]any) error { var tmp DockerProviderConfigDetailed var err = serialization.MapUnmarshalValidate(m, &tmp) if err != nil { @@ -70,7 +70,7 @@ func (cfg *DockerProviderConfig) UnmarshalMap(m map[string]any) gperr.Error { cfg.TLS = tmp.TLS if cfg.TLS != nil { if err := checkFilesOk(cfg.TLS.CAFile, cfg.TLS.CertFile, cfg.TLS.KeyFile); err != nil { - return gperr.Wrap(err) + return err } } return nil diff --git a/internal/types/idlewatcher.go b/internal/types/idlewatcher.go index c93b3445..fdd5940c 100644 --- a/internal/types/idlewatcher.go +++ b/internal/types/idlewatcher.go @@ -1,6 +1,7 @@ package types import ( + "errors" "net/url" "strconv" "strings" @@ -32,7 +33,7 @@ type ( DependsOn []string `json:"depends_on,omitempty"` NoLoadingPage bool `json:"no_loading_page,omitempty"` - valErr gperr.Error + valErr error } // @name IdlewatcherConfig ContainerStopMethod string // @name ContainerStopMethod ContainerSignal string // @name ContainerSignal @@ -57,6 +58,13 @@ const ( ContainerStopMethodKill ContainerStopMethod = "kill" ) +var ( + ErrMissingProviderConfig = errors.New("missing idlewatcher provider config") + ErrInvalidStopMethod = errors.New("invalid stop method") + ErrInvalidStopSignal = errors.New("invalid stop signal") + ErrEmptyStartEndpoint = errors.New("start endpoint must not be empty if defined") +) + func (c *IdlewatcherConfig) Key() string { if c.Docker != nil { return c.Docker.ContainerID @@ -71,7 +79,7 @@ func (c *IdlewatcherConfig) ContainerName() string { return "lxc-" + strconv.Itoa(c.Proxmox.VMID) } -func (c *IdlewatcherConfig) Validate() gperr.Error { +func (c *IdlewatcherConfig) Validate() error { if c.IdleTimeout == 0 { // zero idle timeout means no idle watcher c.valErr = nil return nil @@ -89,13 +97,13 @@ func (c *IdlewatcherConfig) Validate() gperr.Error { return c.valErr } -func (c *IdlewatcherConfig) ValErr() gperr.Error { +func (c *IdlewatcherConfig) ValErr() error { return c.valErr } func (c *IdlewatcherConfig) validateProvider() error { if c.Docker == nil && c.Proxmox == nil { - return gperr.New("missing idlewatcher provider config") + return ErrMissingProviderConfig } return nil } @@ -118,7 +126,7 @@ func (c *IdlewatcherConfig) validateStopMethod() error { case ContainerStopMethodPause, ContainerStopMethodStop, ContainerStopMethodKill: return nil default: - return gperr.New("invalid stop method").Subject(string(c.StopMethod)) + return gperr.PrependSubject(ErrInvalidStopMethod, string(c.StopMethod)) } } @@ -127,7 +135,7 @@ func (c *IdlewatcherConfig) validateStopSignal() error { case "", "SIGINT", "SIGTERM", "SIGQUIT", "SIGHUP", "INT", "TERM", "QUIT", "HUP": return nil default: - return gperr.New("invalid stop signal").Subject(string(c.StopSignal)) + return gperr.PrependSubject(ErrInvalidStopSignal, string(c.StopSignal)) } } @@ -141,7 +149,7 @@ func (c *IdlewatcherConfig) validateStartEndpoint() error { c.StartEndpoint = c.StartEndpoint[:i] } if len(c.StartEndpoint) == 0 { - return gperr.New("start endpoint must not be empty if defined") + return ErrEmptyStartEndpoint } _, err := url.ParseRequestURI(c.StartEndpoint) return err diff --git a/internal/types/routes.go b/internal/types/routes.go index 3aad58bf..c0238015 100644 --- a/internal/types/routes.go +++ b/internal/types/routes.go @@ -8,7 +8,6 @@ import ( "github.com/yusing/godoxy/internal/homepage" nettypes "github.com/yusing/godoxy/internal/net/types" provider "github.com/yusing/godoxy/internal/route/provider/types" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/http/reverseproxy" "github.com/yusing/goutils/pool" "github.com/yusing/goutils/task" @@ -66,8 +65,8 @@ type ( Stream() nettypes.Stream } RouteProvider interface { - Start(task.Parent) gperr.Error - LoadRoutes() gperr.Error + Start(parent task.Parent) error + LoadRoutes() error GetRoute(alias string) (r Route, ok bool) // should be used like `for _, r := range p.IterRoutes` (no braces), not calling it directly IterRoutes(yield func(alias string, r Route) bool) diff --git a/internal/watcher/directory_watcher.go b/internal/watcher/directory_watcher.go index d491ac81..bc061734 100644 --- a/internal/watcher/directory_watcher.go +++ b/internal/watcher/directory_watcher.go @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/watcher/events" - gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" ) @@ -24,7 +23,7 @@ type DirWatcher struct { mu sync.Mutex eventCh chan Event - errCh chan gperr.Error + errCh chan error task *task.Task } @@ -55,14 +54,14 @@ func NewDirectoryWatcher(parent task.Parent, dirPath string) *DirWatcher { w: w, fwMap: make(map[string]*fileWatcher), eventCh: make(chan Event), - errCh: make(chan gperr.Error), + errCh: make(chan error), task: parent.Subtask("dir_watcher("+dirPath+")", true), } go helper.start() return helper } -func (h *DirWatcher) Events(_ context.Context) (<-chan Event, <-chan gperr.Error) { +func (h *DirWatcher) Events(_ context.Context) (<-chan Event, <-chan error) { return h.eventCh, h.errCh } @@ -78,7 +77,7 @@ func (h *DirWatcher) Add(relPath string) Watcher { s = &fileWatcher{ relPath: relPath, eventCh: make(chan Event), - errCh: make(chan gperr.Error), + errCh: make(chan error), } h.fwMap[relPath] = s return s @@ -162,7 +161,7 @@ func (h *DirWatcher) start() { return } select { - case h.errCh <- gperr.Wrap(err): + case h.errCh <- err: default: } } diff --git a/internal/watcher/docker_watcher.go b/internal/watcher/docker_watcher.go index 77b8d2c7..306ede63 100644 --- a/internal/watcher/docker_watcher.go +++ b/internal/watcher/docker_watcher.go @@ -3,6 +3,7 @@ package watcher import ( "context" "errors" + "fmt" "time" dockerEvents "github.com/moby/moby/api/types/events" @@ -11,7 +12,6 @@ import ( "github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/watcher/events" - gperr "github.com/yusing/goutils/errs" ) type ( @@ -82,18 +82,18 @@ func NewDockerWatcher(dockerCfg types.DockerProviderConfig) DockerWatcher { } } -func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) { +func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan error) { return w.EventsWithOptions(ctx, optionsDefault) } -func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan gperr.Error) { +func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan error) { eventCh := make(chan Event) - errCh := make(chan gperr.Error) + errCh := make(chan error) go func() { client, err := docker.NewClient(w.cfg) if err != nil { - errCh <- gperr.Wrap(err, "docker watcher: failed to initialize client") + errCh <- fmt.Errorf("docker watcher: failed to initialize client: %w", err) return } @@ -148,14 +148,14 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList return eventCh, errCh } -func (w DockerWatcher) parseError(err error) gperr.Error { +func (w DockerWatcher) parseError(err error) error { if errors.Is(err, context.DeadlineExceeded) { - return gperr.New("docker client connection timeout") + return errors.New("docker client connection timeout") } if client.IsErrConnectionFailed(err) { - return gperr.New("docker client connection failure") + return errors.New("docker client connection failure") } - return gperr.Wrap(err) + return err } func (w DockerWatcher) handleEvent(event dockerEvents.Message, ch chan<- Event) { diff --git a/internal/watcher/events/README.md b/internal/watcher/events/README.md index 936cb033..43f2e942 100644 --- a/internal/watcher/events/README.md +++ b/internal/watcher/events/README.md @@ -315,7 +315,7 @@ e.onFlush = func(events []Event) { defer func() { if errV := recover(); errV != nil { if err, ok := errV.(error); ok { - e.onError(gperr.Wrap(err).Subject(e.task.Name())) + e.onError(gperr.PrependSubject(e.task.Name(), err)) } else { e.onError(gperr.New("recovered panic in onFlush").Withf("%v", errV).Subject(e.task.Name())) } diff --git a/internal/watcher/events/event_queue.go b/internal/watcher/events/event_queue.go index 40002c47..08755f53 100644 --- a/internal/watcher/events/event_queue.go +++ b/internal/watcher/events/event_queue.go @@ -19,7 +19,7 @@ type ( onError OnErrorFunc } OnFlushFunc = func(events []Event) - OnErrorFunc = func(err gperr.Error) + OnErrorFunc = func(err error) ) const eventQueueCapacity = 10 @@ -50,14 +50,14 @@ func NewEventQueue(queueTask *task.Task, flushInterval time.Duration, onFlush On } } -func (e *EventQueue) Start(eventCh <-chan Event, errCh <-chan gperr.Error) { +func (e *EventQueue) Start(eventCh <-chan Event, errCh <-chan error) { origOnFlush := e.onFlush // recover panic in onFlush when in production mode e.onFlush = func(events []Event) { defer func() { if errV := recover(); errV != nil { if err, ok := errV.(error); ok { - e.onError(gperr.Wrap(err).Subject(e.task.Name())) + e.onError(gperr.PrependSubject(err, e.task.Name())) } else { e.onError(gperr.New("recovered panic in onFlush").Withf("%v", errV).Subject(e.task.Name())) } diff --git a/internal/watcher/file_watcher.go b/internal/watcher/file_watcher.go index c0563981..d28df5cb 100644 --- a/internal/watcher/file_watcher.go +++ b/internal/watcher/file_watcher.go @@ -2,16 +2,14 @@ package watcher import ( "context" - - gperr "github.com/yusing/goutils/errs" ) type fileWatcher struct { relPath string eventCh chan Event - errCh chan gperr.Error + errCh chan error } -func (fw *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) { +func (fw *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan error) { return fw.eventCh, fw.errCh } diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 925d94ed..7068e8ed 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -4,11 +4,10 @@ import ( "context" "github.com/yusing/godoxy/internal/watcher/events" - gperr "github.com/yusing/goutils/errs" ) type Event = events.Event type Watcher interface { - Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) + Events(ctx context.Context) (<-chan Event, <-chan error) }