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

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

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

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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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"))

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")
}
}
}

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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(&notif.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()))
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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(&notif.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(&notif.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)
},
)

View File

@@ -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())

View File

@@ -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)
}

View File

@@ -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:]

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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/<reference>.<format>, e.g. @%s/adguard-home.webp", beforeSlash, beforeSlash)
return fmt.Errorf("%w: expect %s/<reference>.<format>, 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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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).

View File

@@ -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,

View File

@@ -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

View File

@@ -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")

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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]

View File

@@ -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() {

View File

@@ -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)
}
}

View File

@@ -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 ""

View File

@@ -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")
)
```

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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")
}
}

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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")
}
}

View File

@@ -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
}

View File

@@ -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{

View File

@@ -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 != "" {

View File

@@ -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")
)
```

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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

View File

@@ -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)
}
{

View File

@@ -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 {

View File

@@ -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() &lt;-chan struct#123;#125;
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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")
}
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More