refactor: move task, error and testing utils to separte repo; apply gofumpt

This commit is contained in:
yusing
2025-09-27 13:41:50 +08:00
parent 5043ef778f
commit 6776f20332
203 changed files with 696 additions and 2800 deletions

View File

@@ -8,13 +8,12 @@ import (
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/env" "github.com/yusing/godoxy/agent/pkg/env"
"github.com/yusing/godoxy/agent/pkg/server" "github.com/yusing/godoxy/agent/pkg/server"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/metrics/systeminfo" "github.com/yusing/godoxy/internal/metrics/systeminfo"
httpServer "github.com/yusing/godoxy/internal/net/gphttp/server" httpServer "github.com/yusing/godoxy/internal/net/gphttp/server"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/pkg" "github.com/yusing/godoxy/pkg"
socketproxy "github.com/yusing/godoxy/socketproxy/pkg" socketproxy "github.com/yusing/godoxy/socketproxy/pkg"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
) )
func main() { func main() {
@@ -27,21 +26,21 @@ func main() {
ca := &agent.PEMPair{} ca := &agent.PEMPair{}
err := ca.Load(env.AgentCACert) err := ca.Load(env.AgentCACert)
if err != nil { if err != nil {
gperr.LogFatal("init CA error", err) log.Fatal().Err(err).Msg("init CA error")
} }
caCert, err := ca.ToTLSCert() caCert, err := ca.ToTLSCert()
if err != nil { if err != nil {
gperr.LogFatal("init CA error", err) log.Fatal().Err(err).Msg("init CA error")
} }
srv := &agent.PEMPair{} srv := &agent.PEMPair{}
srv.Load(env.AgentSSLCert) srv.Load(env.AgentSSLCert)
if err != nil { if err != nil {
gperr.LogFatal("init SSL error", err) log.Fatal().Err(err).Msg("init SSL error")
} }
srvCert, err := srv.ToTLSCert() srvCert, err := srv.ToTLSCert()
if err != nil { if err != nil {
gperr.LogFatal("init SSL error", err) log.Fatal().Err(err).Msg("init SSL error")
} }
log.Info().Msgf("GoDoxy Agent version %s", pkg.GetVersion()) log.Info().Msgf("GoDoxy Agent version %s", pkg.GetVersion())

View File

@@ -20,7 +20,7 @@ require (
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/yusing/godoxy v0.18.6 github.com/yusing/godoxy v0.18.6
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000 github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
github.com/yusing/goutils v0.2.1 github.com/yusing/goutils v0.3.1
) )
require ( require (

View File

@@ -208,8 +208,8 @@ github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusing/ds v0.2.0 h1:lPhDU5eA2uvquVrBrzLCrQXRJJgSXlUYA53TbuK2sQY= github.com/yusing/ds v0.2.0 h1:lPhDU5eA2uvquVrBrzLCrQXRJJgSXlUYA53TbuK2sQY=
github.com/yusing/ds v0.2.0/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk= github.com/yusing/ds v0.2.0/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk=
github.com/yusing/goutils v0.2.1 h1:KjoCrNO0otthaPCZPfQY+5GKsqs5+J77CxP+TNHYa/Y= github.com/yusing/goutils v0.3.1 h1:xCPoZ/haI8ZJ0ZaPU4g6+okSPdBczs8o98tIZ/TbpsQ=
github.com/yusing/goutils v0.2.1/go.mod h1:v6RZsMRdzcts4udSg0vqUIFvaD0OaUMPTwYJZ4XnQYo= github.com/yusing/goutils v0.3.1/go.mod h1:meg9GcAU8yvBY21JgYjPuLsXD1Q5VdVHE32A4tG5Y5g=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=

View File

@@ -2,15 +2,16 @@ package agent
import ( import (
"iter" "iter"
"os"
"strings"
"github.com/puzpuzpuz/xsync/v4" "github.com/puzpuzpuz/xsync/v4"
"github.com/yusing/godoxy/internal/common"
) )
var agentPool = xsync.NewMap[string, *AgentConfig](xsync.WithPresize(10)) var agentPool = xsync.NewMap[string, *AgentConfig](xsync.WithPresize(10))
func init() { func init() {
if common.IsTest { if strings.HasSuffix(os.Args[0], ".test") {
agentPool.Store("test-agent", &AgentConfig{ agentPool.Store("test-agent", &AgentConfig{
Addr: "test-agent", Addr: "test-agent",
}) })
@@ -63,5 +64,5 @@ func NumAgents() int {
func getAgentByAddr(addr string) (agent *AgentConfig, ok bool) { func getAgentByAddr(addr string) (agent *AgentConfig, ok bool) {
agent, ok = agentPool.Load(addr) agent, ok = agentPool.Load(addr)
return return agent, ok
} }

View File

@@ -3,6 +3,8 @@ package agent
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
@@ -10,14 +12,11 @@ import (
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt"
"io" "io"
"math/big" "math/big"
"strings" "strings"
"time" "time"
"crypto/ecdsa"
"crypto/elliptic"
"fmt"
) )
const ( const (
@@ -244,5 +243,5 @@ func NewAgent() (ca, srv, client *PEMPair, err error) {
} }
client = toPEMPair(clientCertDER, clientKey) client = toPEMPair(clientCertDER, clientKey)
return return ca, srv, client, err
} }

View File

@@ -43,7 +43,7 @@ func proxyConfigFromHeadersLegacy(h http.Header) (cfg Config) {
cfg.Scheme = "https" cfg.Scheme = "https"
} }
return return cfg
} }
func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) { func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {

View File

@@ -10,7 +10,7 @@ import (
"github.com/yusing/godoxy/agent/pkg/env" "github.com/yusing/godoxy/agent/pkg/env"
"github.com/yusing/godoxy/agent/pkg/handler" "github.com/yusing/godoxy/agent/pkg/handler"
"github.com/yusing/godoxy/internal/net/gphttp/server" "github.com/yusing/godoxy/internal/net/gphttp/server"
"github.com/yusing/godoxy/internal/task" "github.com/yusing/goutils/task"
) )
type Options struct { type Options struct {

View File

@@ -9,15 +9,15 @@ import (
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/config" "github.com/yusing/godoxy/internal/config"
"github.com/yusing/godoxy/internal/dnsproviders" "github.com/yusing/godoxy/internal/dnsproviders"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/homepage" "github.com/yusing/godoxy/internal/homepage"
"github.com/yusing/godoxy/internal/logging" "github.com/yusing/godoxy/internal/logging"
"github.com/yusing/godoxy/internal/logging/memlogger" "github.com/yusing/godoxy/internal/logging/memlogger"
"github.com/yusing/godoxy/internal/metrics/systeminfo" "github.com/yusing/godoxy/internal/metrics/systeminfo"
"github.com/yusing/godoxy/internal/metrics/uptime" "github.com/yusing/godoxy/internal/metrics/uptime"
"github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/net/gphttp/middleware"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/pkg" "github.com/yusing/godoxy/pkg"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
func parallel(fns ...func()) { func parallel(fns ...func()) {

2
go.mod
View File

@@ -50,7 +50,7 @@ require (
github.com/yusing/godoxy/agent v0.0.0-20250926130035-55c1c918ba95 github.com/yusing/godoxy/agent v0.0.0-20250926130035-55c1c918ba95
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20250926130035-55c1c918ba95 github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20250926130035-55c1c918ba95
github.com/yusing/godoxy/internal/utils v0.1.0 github.com/yusing/godoxy/internal/utils v0.1.0
github.com/yusing/goutils v0.2.1 github.com/yusing/goutils v0.3.1
) )
require ( require (

4
go.sum
View File

@@ -1648,8 +1648,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusing/ds v0.2.0 h1:lPhDU5eA2uvquVrBrzLCrQXRJJgSXlUYA53TbuK2sQY= github.com/yusing/ds v0.2.0 h1:lPhDU5eA2uvquVrBrzLCrQXRJJgSXlUYA53TbuK2sQY=
github.com/yusing/ds v0.2.0/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk= github.com/yusing/ds v0.2.0/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk=
github.com/yusing/goutils v0.2.1 h1:KjoCrNO0otthaPCZPfQY+5GKsqs5+J77CxP+TNHYa/Y= github.com/yusing/goutils v0.3.1 h1:xCPoZ/haI8ZJ0ZaPU4g6+okSPdBczs8o98tIZ/TbpsQ=
github.com/yusing/goutils v0.2.1/go.mod h1:v6RZsMRdzcts4udSg0vqUIFvaD0OaUMPTwYJZ4XnQYo= github.com/yusing/goutils v0.3.1/go.mod h1:meg9GcAU8yvBY21JgYjPuLsXD1Q5VdVHE32A4tG5Y5g=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=

View File

@@ -7,11 +7,11 @@ import (
"github.com/puzpuzpuz/xsync/v4" "github.com/puzpuzpuz/xsync/v4"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/logging/accesslog" "github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/maxmind" "github.com/yusing/godoxy/internal/maxmind"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
type Config struct { type Config struct {

View File

@@ -4,8 +4,8 @@ import (
"net" "net"
"strings" "strings"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/maxmind" "github.com/yusing/godoxy/internal/maxmind"
gperr "github.com/yusing/goutils/errs"
) )
type MatcherFunc func(*maxmind.IPInfo) bool type MatcherFunc func(*maxmind.IPInfo) bool

View File

@@ -20,7 +20,7 @@ import (
routeApi "github.com/yusing/godoxy/internal/api/v1/route" routeApi "github.com/yusing/godoxy/internal/api/v1/route"
"github.com/yusing/godoxy/internal/auth" "github.com/yusing/godoxy/internal/auth"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
) )
// @title GoDoxy API // @title GoDoxy API

View File

@@ -3,7 +3,7 @@ package apitypes
import ( import (
"errors" "errors"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
) )
type ErrorResponse struct { type ErrorResponse struct {

View File

@@ -9,7 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
apitypes "github.com/yusing/godoxy/internal/api/types" apitypes "github.com/yusing/goutils/apitypes"
) )
type NewAgentRequest struct { type NewAgentRequest struct {

View File

@@ -8,8 +8,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/certs" "github.com/yusing/godoxy/agent/pkg/certs"
. "github.com/yusing/godoxy/internal/api/types"
config "github.com/yusing/godoxy/internal/config/types" config "github.com/yusing/godoxy/internal/config/types"
apitypes "github.com/yusing/goutils/apitypes"
) )
type VerifyNewAgentRequest struct { type VerifyNewAgentRequest struct {
@@ -35,44 +35,44 @@ type VerifyNewAgentRequest struct {
func Verify(c *gin.Context) { func Verify(c *gin.Context) {
var request VerifyNewAgentRequest var request VerifyNewAgentRequest
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, Error("invalid request", err)) c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return return
} }
filename, ok := certs.AgentCertsFilepath(request.Host) filename, ok := certs.AgentCertsFilepath(request.Host)
if !ok { if !ok {
c.JSON(http.StatusBadRequest, Error("invalid host", nil)) c.JSON(http.StatusBadRequest, apitypes.Error("invalid host", nil))
return return
} }
ca, err := fromEncryptedPEMPairResponse(request.CA) ca, err := fromEncryptedPEMPairResponse(request.CA)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, Error("invalid CA", err)) c.JSON(http.StatusBadRequest, apitypes.Error("invalid CA", err))
return return
} }
client, err := fromEncryptedPEMPairResponse(request.Client) client, err := fromEncryptedPEMPairResponse(request.Client)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, Error("invalid client", err)) c.JSON(http.StatusBadRequest, apitypes.Error("invalid client", err))
return return
} }
nRoutesAdded, err := config.GetInstance().VerifyNewAgent(request.Host, ca, client, request.ContainerRuntime) nRoutesAdded, err := config.GetInstance().VerifyNewAgent(request.Host, ca, client, request.ContainerRuntime)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, Error("invalid request", err)) c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return return
} }
zip, err := certs.ZipCert(ca.Cert, client.Cert, client.Key) zip, err := certs.ZipCert(ca.Cert, client.Cert, client.Key)
if err != nil { if err != nil {
c.Error(InternalServerError(err, "failed to zip certs")) c.Error(apitypes.InternalServerError(err, "failed to zip certs"))
return return
} }
if err := os.WriteFile(filename, zip, 0o600); err != nil { if err := os.WriteFile(filename, zip, 0o600); err != nil {
c.Error(InternalServerError(err, "failed to write certs")) c.Error(apitypes.InternalServerError(err, "failed to write certs"))
return return
} }
c.JSON(http.StatusOK, Success(fmt.Sprintf("Added %d routes", nRoutesAdded))) c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded)))
} }

View File

@@ -8,9 +8,9 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
apitypes "github.com/yusing/godoxy/internal/api/types" apitypes "github.com/yusing/godoxy/internal/api/types"
config "github.com/yusing/godoxy/internal/config/types" config "github.com/yusing/godoxy/internal/config/types"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/logging/memlogger" "github.com/yusing/godoxy/internal/logging/memlogger"
"github.com/yusing/godoxy/internal/net/gphttp/websocket" "github.com/yusing/godoxy/internal/net/gphttp/websocket"
gperr "github.com/yusing/goutils/errs"
) )
// @x-id "renew" // @x-id "renew"

View File

@@ -6,7 +6,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
) )
type ContainerState = container.ContainerState // @name ContainerState type ContainerState = container.ContainerState // @name ContainerState

View File

@@ -6,7 +6,7 @@ import (
dockerSystem "github.com/docker/docker/api/types/system" dockerSystem "github.com/docker/docker/api/types/system"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
) )

View File

@@ -13,7 +13,7 @@ import (
apitypes "github.com/yusing/godoxy/internal/api/types" apitypes "github.com/yusing/godoxy/internal/api/types"
"github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/net/gphttp/websocket" "github.com/yusing/godoxy/internal/net/gphttp/websocket"
"github.com/yusing/godoxy/internal/task" "github.com/yusing/goutils/task"
) )
type LogsQueryParams struct { type LogsQueryParams struct {

View File

@@ -10,8 +10,8 @@ import (
apitypes "github.com/yusing/godoxy/internal/api/types" apitypes "github.com/yusing/godoxy/internal/api/types"
config "github.com/yusing/godoxy/internal/config/types" config "github.com/yusing/godoxy/internal/config/types"
"github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/net/gphttp/websocket" "github.com/yusing/godoxy/internal/net/gphttp/websocket"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/http/httpheaders" "github.com/yusing/goutils/http/httpheaders"
) )

View File

@@ -6,9 +6,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
apitypes "github.com/yusing/godoxy/internal/api/types" apitypes "github.com/yusing/godoxy/internal/api/types"
config "github.com/yusing/godoxy/internal/config/types" config "github.com/yusing/godoxy/internal/config/types"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/net/gphttp/middleware"
"github.com/yusing/godoxy/internal/route/provider" "github.com/yusing/godoxy/internal/route/provider"
gperr "github.com/yusing/goutils/errs"
) )
type ValidateFileRequest struct { type ValidateFileRequest struct {

View File

@@ -14,10 +14,10 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
apitypes "github.com/yusing/godoxy/internal/api/types" apitypes "github.com/yusing/godoxy/internal/api/types"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/metrics/period" "github.com/yusing/godoxy/internal/metrics/period"
"github.com/yusing/godoxy/internal/metrics/systeminfo" "github.com/yusing/godoxy/internal/metrics/systeminfo"
"github.com/yusing/godoxy/internal/net/gphttp/websocket" "github.com/yusing/godoxy/internal/net/gphttp/websocket"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/http/httpheaders" "github.com/yusing/goutils/http/httpheaders"
"github.com/yusing/goutils/synk" "github.com/yusing/goutils/synk"
) )

View File

@@ -15,9 +15,9 @@ import (
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/net/gphttp" "github.com/yusing/godoxy/internal/net/gphttp"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
gperr "github.com/yusing/goutils/errs"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )

View File

@@ -16,7 +16,7 @@ import (
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"golang.org/x/oauth2" "golang.org/x/oauth2"
. "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
// setupMockOIDC configures mock OIDC provider for testing. // setupMockOIDC configures mock OIDC provider for testing.
@@ -35,7 +35,7 @@ func setupMockOIDC(t *testing.T) {
}, },
Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}, },
endSessionURL: Must(url.Parse("http://mock-provider/logout")), endSessionURL: expect.Must(url.Parse("http://mock-provider/logout")),
oidcProvider: provider, oidcProvider: provider,
oidcVerifier: provider.Verifier(&oidc.Config{ oidcVerifier: provider.Verifier(&oidc.Config{
ClientID: "test-client", ClientID: "test-client",
@@ -75,7 +75,7 @@ func (j *provider) SignClaims(t *testing.T, claims jwt.Claims) string {
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
token.Header["kid"] = keyID token.Header["kid"] = keyID
signed, err := token.SignedString(j.key) signed, err := token.SignedString(j.key)
ExpectNoError(t, err) expect.NoError(t, err)
return signed return signed
} }
@@ -84,7 +84,7 @@ func setupProvider(t *testing.T) *provider {
// Generate an RSA key pair for the test. // Generate an RSA key pair for the test.
privKey, err := rsa.GenerateKey(rand.Reader, 2048) privKey, err := rsa.GenerateKey(rand.Reader, 2048)
ExpectNoError(t, err) expect.NoError(t, err)
// Build the matching public JWK that will be served by the endpoint. // Build the matching public JWK that will be served by the endpoint.
jwk := buildRSAJWK(t, &privKey.PublicKey, keyID) jwk := buildRSAJWK(t, &privKey.PublicKey, keyID)
@@ -227,12 +227,12 @@ func TestOIDCCallbackHandler(t *testing.T) {
} }
if tt.wantStatus == http.StatusTemporaryRedirect { if tt.wantStatus == http.StatusTemporaryRedirect {
setCookie := Must(http.ParseSetCookie(w.Header().Get("Set-Cookie"))) setCookie := expect.Must(http.ParseSetCookie(w.Header().Get("Set-Cookie")))
ExpectEqual(t, setCookie.Name, CookieOauthToken) expect.Equal(t, setCookie.Name, CookieOauthToken)
ExpectTrue(t, setCookie.Value != "") expect.True(t, setCookie.Value != "")
ExpectEqual(t, setCookie.Path, "/") expect.Equal(t, setCookie.Path, "/")
ExpectEqual(t, setCookie.SameSite, http.SameSiteLaxMode) expect.Equal(t, setCookie.SameSite, http.SameSiteLaxMode)
ExpectEqual(t, setCookie.HttpOnly, true) expect.Equal(t, setCookie.HttpOnly, true)
} }
}) })
} }
@@ -245,7 +245,7 @@ func TestInitOIDC(t *testing.T) {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
ExpectNoError(t, json.NewEncoder(w).Encode(discoveryDocument(t, server))) expect.NoError(t, json.NewEncoder(w).Encode(discoveryDocument(t, server)))
}) })
server = httptest.NewServer(mux) server = httptest.NewServer(mux)
t.Cleanup(server.Close) t.Cleanup(server.Close)
@@ -445,9 +445,9 @@ func TestCheckToken(t *testing.T) {
// Call CheckToken and verify the result. // Call CheckToken and verify the result.
err := auth.CheckToken(req) err := auth.CheckToken(req)
if tc.wantErr == nil { if tc.wantErr == nil {
ExpectNoError(t, err) expect.NoError(t, err)
} else { } else {
ExpectError(t, tc.wantErr, err) expect.ErrorIs(t, tc.wantErr, err)
} }
}) })
} }

View File

@@ -8,8 +8,8 @@ import (
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/net/gphttp" "github.com/yusing/godoxy/internal/net/gphttp"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )

View File

@@ -9,14 +9,14 @@ import (
"testing" "testing"
"time" "time"
. "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
func newMockUserPassAuth() *UserPassAuth { func newMockUserPassAuth() *UserPassAuth {
return &UserPassAuth{ return &UserPassAuth{
username: "username", username: "username",
pwdHash: Must(bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)), pwdHash: expect.Must(bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)),
secret: []byte("abcdefghijklmnopqrstuvwxyz"), secret: []byte("abcdefghijklmnopqrstuvwxyz"),
tokenTTL: time.Hour, tokenTTL: time.Hour,
} }
@@ -25,17 +25,17 @@ func newMockUserPassAuth() *UserPassAuth {
func TestUserPassValidateCredentials(t *testing.T) { func TestUserPassValidateCredentials(t *testing.T) {
auth := newMockUserPassAuth() auth := newMockUserPassAuth()
err := auth.validatePassword("username", "password") err := auth.validatePassword("username", "password")
ExpectNoError(t, err) expect.NoError(t, err)
err = auth.validatePassword("username", "wrong-password") err = auth.validatePassword("username", "wrong-password")
ExpectError(t, ErrInvalidPassword, err) expect.ErrorIs(t, ErrInvalidPassword, err)
err = auth.validatePassword("wrong-username", "password") err = auth.validatePassword("wrong-username", "password")
ExpectError(t, ErrInvalidUsername, err) expect.ErrorIs(t, ErrInvalidUsername, err)
} }
func TestUserPassCheckToken(t *testing.T) { func TestUserPassCheckToken(t *testing.T) {
auth := newMockUserPassAuth() auth := newMockUserPassAuth()
token, err := auth.NewToken() token, err := auth.NewToken()
ExpectNoError(t, err) expect.NoError(t, err)
tests := []struct { tests := []struct {
token string token string
wantErr bool wantErr bool
@@ -60,9 +60,9 @@ func TestUserPassCheckToken(t *testing.T) {
} }
err = auth.CheckToken(req) err = auth.CheckToken(req)
if tt.wantErr { if tt.wantErr {
ExpectTrue(t, err != nil) expect.True(t, err != nil)
} else { } else {
ExpectNoError(t, err) expect.NoError(t, err)
} }
} }
} }
@@ -96,20 +96,20 @@ func TestUserPassLoginCallbackHandler(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
req := &http.Request{ req := &http.Request{
Host: "app.example.com", Host: "app.example.com",
Body: io.NopCloser(bytes.NewReader(Must(json.Marshal(tt.creds)))), Body: io.NopCloser(bytes.NewReader(expect.Must(json.Marshal(tt.creds)))),
} }
auth.PostAuthCallbackHandler(w, req) auth.PostAuthCallbackHandler(w, req)
if tt.wantErr { if tt.wantErr {
ExpectEqual(t, w.Code, http.StatusUnauthorized) expect.Equal(t, w.Code, http.StatusUnauthorized)
} else { } else {
setCookie := Must(http.ParseSetCookie(w.Header().Get("Set-Cookie"))) setCookie := expect.Must(http.ParseSetCookie(w.Header().Get("Set-Cookie")))
ExpectTrue(t, setCookie.Name == auth.TokenCookieName()) expect.True(t, setCookie.Name == auth.TokenCookieName())
ExpectTrue(t, setCookie.Value != "") expect.True(t, setCookie.Value != "")
ExpectEqual(t, setCookie.Domain, "example.com") expect.Equal(t, setCookie.Domain, "example.com")
ExpectEqual(t, setCookie.Path, "/") expect.Equal(t, setCookie.Path, "/")
ExpectEqual(t, setCookie.SameSite, http.SameSiteLaxMode) expect.Equal(t, setCookie.SameSite, http.SameSiteLaxMode)
ExpectEqual(t, setCookie.HttpOnly, true) expect.Equal(t, setCookie.HttpOnly, true)
ExpectEqual(t, w.Code, http.StatusOK) expect.Equal(t, w.Code, http.StatusOK)
} }
} }
} }

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
) )

View File

@@ -15,8 +15,8 @@ import (
"github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/lego"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
gperr "github.com/yusing/goutils/errs"
) )
type Config struct { type Config struct {

View File

@@ -18,10 +18,10 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/notif"
"github.com/yusing/godoxy/internal/task" gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
) )
type ( type (

View File

@@ -2,8 +2,8 @@ package autocert
import ( import (
"github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
) )
type Generator func(map[string]any) (challenge.Provider, gperr.Error) type Generator func(map[string]any) (challenge.Provider, gperr.Error)

View File

@@ -3,7 +3,7 @@ package autocert
import ( import (
"crypto/tls" "crypto/tls"
"github.com/yusing/godoxy/internal/task" "github.com/yusing/goutils/task"
) )
type Provider interface { type Provider interface {

View File

@@ -2,8 +2,8 @@ package config
import ( import (
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/route/provider" "github.com/yusing/godoxy/internal/route/provider"
gperr "github.com/yusing/goutils/errs"
) )
func (cfg *Config) VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) { func (cfg *Config) VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) {

View File

@@ -18,17 +18,17 @@ import (
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
config "github.com/yusing/godoxy/internal/config/types" config "github.com/yusing/godoxy/internal/config/types"
"github.com/yusing/godoxy/internal/entrypoint" "github.com/yusing/godoxy/internal/entrypoint"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/maxmind" "github.com/yusing/godoxy/internal/maxmind"
"github.com/yusing/godoxy/internal/net/gphttp/server" "github.com/yusing/godoxy/internal/net/gphttp/server"
"github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/notif"
"github.com/yusing/godoxy/internal/proxmox" "github.com/yusing/godoxy/internal/proxmox"
proxy "github.com/yusing/godoxy/internal/route/provider" proxy "github.com/yusing/godoxy/internal/route/provider"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/watcher" "github.com/yusing/godoxy/internal/watcher"
"github.com/yusing/godoxy/internal/watcher/events" "github.com/yusing/godoxy/internal/watcher/events"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/strings/ansi" "github.com/yusing/goutils/strings/ansi"
"github.com/yusing/goutils/task"
) )
type Config struct { type Config struct {

View File

@@ -9,13 +9,13 @@ import (
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/acl" "github.com/yusing/godoxy/internal/acl"
"github.com/yusing/godoxy/internal/autocert" "github.com/yusing/godoxy/internal/autocert"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/logging/accesslog" "github.com/yusing/godoxy/internal/logging/accesslog"
maxmind "github.com/yusing/godoxy/internal/maxmind/types" maxmind "github.com/yusing/godoxy/internal/maxmind/types"
"github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/notif"
"github.com/yusing/godoxy/internal/proxmox" "github.com/yusing/godoxy/internal/proxmox"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
gperr "github.com/yusing/goutils/errs"
) )
type ( type (

View File

@@ -1,7 +1,9 @@
package dnsproviders package dnsproviders
type DummyConfig struct{} type (
type DummyProvider struct{} DummyConfig struct{}
DummyProvider struct{}
)
func NewDummyDefaultConfig() *DummyConfig { func NewDummyDefaultConfig() *DummyConfig {
return &DummyConfig{} return &DummyConfig{}

View File

@@ -148,7 +148,7 @@ require (
github.com/vultr/govultr/v3 v3.24.0 // indirect github.com/vultr/govultr/v3 v3.24.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/yusing/godoxy/internal/utils v0.1.0 // indirect github.com/yusing/godoxy/internal/utils v0.1.0 // indirect
github.com/yusing/goutils v0.2.1 // indirect github.com/yusing/goutils v0.3.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect

View File

@@ -1513,8 +1513,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusing/goutils v0.2.1 h1:KjoCrNO0otthaPCZPfQY+5GKsqs5+J77CxP+TNHYa/Y= github.com/yusing/goutils v0.3.1 h1:xCPoZ/haI8ZJ0ZaPU4g6+okSPdBczs8o98tIZ/TbpsQ=
github.com/yusing/goutils v0.2.1/go.mod h1:v6RZsMRdzcts4udSg0vqUIFvaD0OaUMPTwYJZ4XnQYo= github.com/yusing/goutils v0.3.1/go.mod h1:meg9GcAU8yvBY21JgYjPuLsXD1Q5VdVHE32A4tG5Y5g=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=

View File

@@ -16,7 +16,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/task" "github.com/yusing/goutils/task"
) )
// TODO: implement reconnect here. // TODO: implement reconnect here.

View File

@@ -14,10 +14,10 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
gperr "github.com/yusing/goutils/errs"
) )
var DummyContainer = new(types.Container) var DummyContainer = new(types.Container)
@@ -84,7 +84,7 @@ func FromDocker(c *container.Summary, dockerHost string) (res *types.Container)
if res.PrivateHostname == "" && res.PublicHostname == "" && res.Running { if res.PrivateHostname == "" && res.PublicHostname == "" && res.Running {
addError(res, ErrNoNetwork) addError(res, ErrNoNetwork)
} }
return return res
} }
func IsBlacklisted(c *types.Container) bool { func IsBlacklisted(c *types.Container) bool {

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
. "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
func TestContainerExplicit(t *testing.T) { func TestContainerExplicit(t *testing.T) {
@@ -37,7 +37,7 @@ func TestContainerExplicit(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := FromDocker(&container.Summary{Names: []string{"test"}, State: "test", Labels: tt.labels}, "") c := FromDocker(&container.Summary{Names: []string{"test"}, State: "test", Labels: tt.labels}, "")
ExpectEqual(t, c.IsExplicit, tt.isExplicit) expect.Equal(t, c.IsExplicit, tt.isExplicit)
}) })
} }
} }
@@ -74,7 +74,7 @@ func TestContainerHostNetworkMode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := FromDocker(tt.container, "") c := FromDocker(tt.container, "")
ExpectEqual(t, c.IsHostNetworkMode, tt.isHostNetworkMode) expect.Equal(t, c.IsHostNetworkMode, tt.isHostNetworkMode)
}) })
} }
} }
@@ -109,9 +109,9 @@ func TestImageNameParsing(t *testing.T) {
t.Run(tt.full, func(t *testing.T) { t.Run(tt.full, func(t *testing.T) {
helper := containerHelper{&container.Summary{Image: tt.full}} helper := containerHelper{&container.Summary{Image: tt.full}}
im := helper.parseImage() im := helper.parseImage()
ExpectEqual(t, im.Author, tt.author) expect.Equal(t, im.Author, tt.author)
ExpectEqual(t, im.Name, tt.image) expect.Equal(t, im.Name, tt.image)
ExpectEqual(t, im.Tag, tt.tag) expect.Equal(t, im.Tag, tt.tag)
}) })
} }
} }

View File

@@ -5,8 +5,8 @@ import (
"strings" "strings"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
) )

View File

@@ -11,8 +11,8 @@ import (
"github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/net/gphttp/middleware"
"github.com/yusing/godoxy/internal/net/gphttp/middleware/errorpage" "github.com/yusing/godoxy/internal/net/gphttp/middleware/errorpage"
"github.com/yusing/godoxy/internal/route/routes" "github.com/yusing/godoxy/internal/route/routes"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
) )
type Entrypoint struct { type Entrypoint struct {
@@ -56,15 +56,15 @@ func (ep *Entrypoint) SetMiddlewares(mws []map[string]any) error {
func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.RequestLoggerConfig) (err error) { func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.RequestLoggerConfig) (err error) {
if cfg == nil { if cfg == nil {
ep.accessLogger = nil ep.accessLogger = nil
return return err
} }
ep.accessLogger, err = accesslog.NewAccessLogger(parent, cfg) ep.accessLogger, err = accesslog.NewAccessLogger(parent, cfg)
if err != nil { if err != nil {
return return err
} }
log.Debug().Msg("entrypoint access logger created") log.Debug().Msg("entrypoint access logger created")
return return err
} }
func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {

View File

@@ -12,8 +12,8 @@ import (
"github.com/yusing/godoxy/internal/route" "github.com/yusing/godoxy/internal/route"
"github.com/yusing/godoxy/internal/route/routes" "github.com/yusing/godoxy/internal/route/routes"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
) )
type noopResponseWriter struct { type noopResponseWriter struct {
@@ -24,10 +24,12 @@ type noopResponseWriter struct {
func (w *noopResponseWriter) Header() http.Header { func (w *noopResponseWriter) Header() http.Header {
return http.Header{} return http.Header{}
} }
func (w *noopResponseWriter) Write(b []byte) (int, error) { func (w *noopResponseWriter) Write(b []byte) (int, error) {
w.written = b w.written = b
return len(b), nil return len(b), nil
} }
func (w *noopResponseWriter) WriteHeader(statusCode int) { func (w *noopResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode w.statusCode = statusCode
} }
@@ -45,7 +47,7 @@ func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
func BenchmarkEntrypointReal(b *testing.B) { func BenchmarkEntrypointReal(b *testing.B) {
var ep Entrypoint var ep Entrypoint
var req = http.Request{ req := http.Request{
Method: "GET", Method: "GET",
URL: &url.URL{Path: "/", RawPath: "/"}, URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld", Host: "test.domain.tld",
@@ -107,7 +109,7 @@ func BenchmarkEntrypointReal(b *testing.B) {
func BenchmarkEntrypoint(b *testing.B) { func BenchmarkEntrypoint(b *testing.B) {
var ep Entrypoint var ep Entrypoint
var req = http.Request{ req := http.Request{
Method: "GET", Method: "GET",
URL: &url.URL{Path: "/", RawPath: "/"}, URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld", Host: "test.domain.tld",

View File

@@ -6,7 +6,7 @@ import (
"github.com/yusing/godoxy/internal/route" "github.com/yusing/godoxy/internal/route"
"github.com/yusing/godoxy/internal/route/routes" "github.com/yusing/godoxy/internal/route/routes"
expect "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
var ep = NewEntrypoint() var ep = NewEntrypoint()

View File

@@ -1,106 +0,0 @@
# gperr
gperr is an error interface that supports nested structure and subject highlighting.
## Usage
### gperr.Error
The error interface.
### gperr.New
Like `errors.New`, but returns a `gperr.Error`.
### gperr.Wrap
Like `fmt.Errorf("%s: %w", message, err)`, but returns a `gperr.Error`.
### gperr.Error.Subject
Returns a new error with the subject prepended to the error message. The main subject is highlighted.
```go
err := gperr.New("error message")
err = err.Subject("bar")
err = err.Subject("foo")
```
Output:
<code>foo > <span style="color: red;">bar</span>: error message</code>
### gperr.Error.Subjectf
Like `gperr.Error.Subject`, but formats the subject with `fmt.Sprintf`.
### gperr.PrependSubject
Prepends the subject to the error message like `gperr.Error.Subject`.
```go
err := gperr.New("error message")
err = gperr.PrependSubject(err, "foo")
err = gperr.PrependSubject(err, "bar")
```
Output:
<code>bar > <span style="color: red;">foo</span>: error message</code>
### gperr.Error.With
Adds a new error to the error chain.
```go
err := gperr.New("error message")
err = err.With(gperr.New("inner error"))
err = err.With(gperr.New("inner error 2").With(gperr.New("inner inner error")))
```
Output:
```
error message:
• inner error
• inner error 2
• inner inner error
```
### gperr.Error.Withf
Like `gperr.Error.With`, but formats the error with `fmt.Errorf`.
### gperr.Error.Is
Returns true if the error is equal to the given error.
### gperr.Builder
A builder for `gperr.Error`.
```go
builder := gperr.NewBuilder("foo")
builder.Add(gperr.New("error message"))
builder.Addf("error message: %s", "foo")
builder.AddRange(gperr.New("error message 1"), gperr.New("error message 2"))
```
Output:
```
foo:
• error message
• error message: foo
• error message 1
• error message 2
```
### gperr.Builder.Build
Builds a `gperr.Error` from the builder.
## When to return gperr.Error
- When you want to return multiple errors
- When the error has a subject

View File

@@ -1,75 +0,0 @@
package gperr
import (
"encoding/json"
"errors"
"fmt"
)
// baseError is an immutable wrapper around an error.
//
//nolint:recvcheck
type baseError struct {
Err error `json:"err"`
}
func (err *baseError) Unwrap() error {
return err.Err
}
func (err *baseError) Is(other error) bool {
if other, ok := other.(*baseError); ok {
return errors.Is(err.Err, other.Err)
}
return errors.Is(err.Err, other)
}
func (err baseError) Subject(subject string) Error {
err.Err = PrependSubject(subject, err.Err)
return &err
}
func (err *baseError) Subjectf(format string, args ...any) Error {
if len(args) > 0 {
return err.Subject(fmt.Sprintf(format, args...))
}
return err.Subject(format)
}
func (err *baseError) With(extra error) Error {
if extra == nil {
return err
}
return &nestedError{&baseError{err.Err}, []error{extra}}
}
func (err baseError) Withf(format string, args ...any) Error {
return &nestedError{&err, []error{fmt.Errorf(format, args...)}}
}
func (err *baseError) Error() string {
return err.Err.Error()
}
// MarshalJSON implements the json.Marshaler interface.
func (err *baseError) MarshalJSON() ([]byte, error) {
//nolint:errorlint
switch err := err.Err.(type) {
case Error, *withSubject:
return json.Marshal(err)
case json.Marshaler:
return err.MarshalJSON()
case interface{ MarshalText() ([]byte, error) }:
return err.MarshalText()
default:
return json.Marshal(err.Error())
}
}
func (err *baseError) Plain() []byte {
return Plain(err.Err)
}
func (err *baseError) Markdown() []byte {
return Markdown(err.Err)
}

View File

@@ -1,161 +0,0 @@
package gperr
import (
"fmt"
"sync"
)
type noLock struct{}
func (noLock) Lock() {}
func (noLock) Unlock() {}
func (noLock) RLock() {}
func (noLock) RUnlock() {}
type rwLock interface {
sync.Locker
RLock()
RUnlock()
}
type Builder struct {
about string
errs []error
rwLock
}
// NewBuilder creates a new Builder.
//
// If about is not provided, the Builder will not have a subject
// and will expand when adding to another builder.
func NewBuilder(about ...string) *Builder {
if len(about) == 0 {
return &Builder{rwLock: noLock{}}
}
return &Builder{about: about[0], rwLock: noLock{}}
}
func NewBuilderWithConcurrency(about ...string) *Builder {
if len(about) == 0 {
return &Builder{rwLock: new(sync.RWMutex)}
}
return &Builder{about: about[0], rwLock: new(sync.RWMutex)}
}
func (b *Builder) EnableConcurrency() {
b.rwLock = new(sync.RWMutex)
}
func (b *Builder) About() string {
return b.about
}
func (b *Builder) HasError() bool {
// no need to lock, when this is called, the Builder is not used anymore
return len(b.errs) > 0
}
func (b *Builder) Error() Error {
if len(b.errs) == 0 {
return nil
}
if len(b.errs) == 1 && b.about == "" {
return wrap(b.errs[0])
}
return &nestedError{Err: New(b.about), Extras: b.errs}
}
func (b *Builder) String() string {
err := b.Error()
if err == nil {
return ""
}
return err.Error()
}
// Add adds an error to the Builder.
//
// adding nil is no-op.
func (b *Builder) Add(err error) {
if err == nil {
return
}
b.Lock()
defer b.Unlock()
b.add(err)
}
func (b *Builder) Adds(err string) {
b.Lock()
defer b.Unlock()
b.errs = append(b.errs, newError(err))
}
func (b *Builder) Addf(format string, args ...any) {
if len(args) > 0 {
b.Lock()
defer b.Unlock()
b.errs = append(b.errs, fmt.Errorf(format, args...))
} else {
b.Adds(format)
}
}
func (b *Builder) AddFrom(other *Builder, flatten bool) {
if other == nil || !other.HasError() {
return
}
b.Lock()
defer b.Unlock()
if flatten {
b.errs = append(b.errs, other.errs...)
} else {
b.errs = append(b.errs, other.Error())
}
}
func (b *Builder) AddRange(errs ...error) {
nonNilErrs := make([]error, 0, len(errs))
for _, err := range errs {
if err != nil {
nonNilErrs = append(nonNilErrs, err)
}
}
b.Lock()
defer b.Unlock()
for _, err := range nonNilErrs {
b.add(err)
}
}
func (b *Builder) ForEach(fn func(error)) {
b.RLock()
errs := b.errs
b.RUnlock()
for _, err := range errs {
fn(err)
}
}
func (b *Builder) add(err error) {
switch err := err.(type) { //nolint:errorlint
case *baseError:
b.errs = append(b.errs, err.Err)
case *nestedError:
if err.Err == nil {
b.errs = append(b.errs, err.Extras...)
} else {
b.errs = append(b.errs, err)
}
case *MultilineError:
b.add(&err.nestedError)
default:
b.errs = append(b.errs, err)
}
}

View File

@@ -1,56 +0,0 @@
package gperr_test
import (
"context"
"errors"
"io"
"testing"
. "github.com/yusing/godoxy/internal/gperr"
. "github.com/yusing/godoxy/internal/utils/testing"
)
func TestBuilderEmpty(t *testing.T) {
eb := NewBuilder("foo")
ExpectTrue(t, errors.Is(eb.Error(), nil))
ExpectFalse(t, eb.HasError())
}
func TestBuilderAddNil(t *testing.T) {
eb := NewBuilder("foo")
var err Error
for range 3 {
eb.Add(nil)
}
for range 3 {
eb.Add(err)
}
eb.AddRange(nil, nil, err)
ExpectFalse(t, eb.HasError())
ExpectTrue(t, eb.Error() == nil)
}
func TestBuilderIs(t *testing.T) {
eb := NewBuilder("foo")
eb.Add(context.Canceled)
eb.Add(io.ErrShortBuffer)
ExpectTrue(t, eb.HasError())
ExpectError(t, io.ErrShortBuffer, eb.Error())
ExpectError(t, context.Canceled, eb.Error())
}
func TestBuilderNested(t *testing.T) {
eb := NewBuilder("action failed")
eb.Add(New("Action 1").Withf("Inner: 1").Withf("Inner: 2"))
eb.Add(New("Action 2").Withf("Inner: 3"))
got := eb.String()
expected := `action failed
• Action 1
• Inner: 1
• Inner: 2
• Action 2
• Inner: 3
`
ExpectEqual(t, got, expected)
}

View File

@@ -1,43 +0,0 @@
package gperr
type Error interface {
error
// Is is a wrapper for errors.Is when there is no sub-error.
//
// When there are sub-errors, they will also be checked.
Is(other error) bool
// With appends a sub-error to the error.
With(extra error) Error
// Withf is a wrapper for With(fmt.Errorf(format, args...)).
Withf(format string, args ...any) Error
// Subject prepends the given subject with a colon and space to the error message.
//
// If there is already a subject in the error message, the subject will be
// prepended to the existing subject with " > ".
//
// Subject empty string is ignored.
Subject(subject string) Error
// Subjectf is a wrapper for Subject(fmt.Sprintf(format, args...)).
Subjectf(format string, args ...any) Error
PlainError
MarkdownError
}
type PlainError interface {
Plain() []byte
}
type MarkdownError interface {
Markdown() []byte
}
// this makes JSON marshaling work,
// as the builtin one doesn't.
//
//nolint:errname
type errStr string
func (err errStr) Error() string {
return string(err)
}

View File

@@ -1,159 +0,0 @@
package gperr
import (
"errors"
"strings"
"testing"
expect "github.com/yusing/godoxy/internal/utils/testing"
"github.com/yusing/goutils/strings/ansi"
)
func TestBaseString(t *testing.T) {
expect.Equal(t, New("error").Error(), "error")
}
func TestBaseWithSubject(t *testing.T) {
err := New("error")
withSubject := err.Subject("foo")
withSubjectf := err.Subjectf("%s %s", "foo", "bar")
expect.ErrorIs(t, err, withSubject)
expect.Equal(t, ansi.StripANSI(withSubject.Error()), "foo: error")
expect.True(t, withSubject.Is(err))
expect.ErrorIs(t, err, withSubjectf)
expect.Equal(t, ansi.StripANSI(withSubjectf.Error()), "foo bar: error")
expect.True(t, withSubjectf.Is(err))
}
func TestBaseWithExtra(t *testing.T) {
err := New("error")
extra := New("bar").Subject("baz")
withExtra := err.With(extra)
expect.True(t, withExtra.Is(extra))
expect.True(t, withExtra.Is(err))
expect.True(t, errors.Is(withExtra, extra))
expect.True(t, errors.Is(withExtra, err))
expect.True(t, strings.Contains(withExtra.Error(), err.Error()))
expect.True(t, strings.Contains(withExtra.Error(), extra.Error()))
expect.True(t, strings.Contains(withExtra.Error(), "baz"))
}
func TestBaseUnwrap(t *testing.T) {
err := errors.New("err")
wrapped := Wrap(err)
expect.ErrorIs(t, err, errors.Unwrap(wrapped))
}
func TestNestedUnwrap(t *testing.T) {
err := errors.New("err")
err2 := New("err2")
wrapped := Wrap(err).Subject("foo").With(err2.Subject("bar"))
unwrapper, ok := wrapped.(interface{ Unwrap() []error })
expect.True(t, ok)
expect.ErrorIs(t, err, wrapped)
expect.ErrorIs(t, err2, wrapped)
expect.Equal(t, len(unwrapper.Unwrap()), 2)
}
func TestErrorIs(t *testing.T) {
from := errors.New("error")
err := Wrap(from)
expect.ErrorIs(t, from, err)
expect.True(t, err.Is(from))
expect.False(t, err.Is(New("error")))
expect.True(t, errors.Is(err.Subject("foo"), from))
expect.True(t, errors.Is(err.Withf("foo"), from))
expect.True(t, errors.Is(err.Subject("foo").Withf("bar"), from))
}
func TestErrorImmutability(t *testing.T) {
err := New("err")
err2 := New("err2")
for range 3 {
// t.Logf("%d: %v %T %s", i, errors.Unwrap(err), err, err)
_ = err.Subject("foo")
expect.False(t, strings.Contains(err.Error(), "foo"))
_ = err.With(err2)
expect.False(t, strings.Contains(err.Error(), "extra"))
expect.False(t, err.Is(err2))
err = err.Subject("bar").Withf("baz")
expect.True(t, err != nil)
}
}
func TestErrorWith(t *testing.T) {
err1 := New("err1")
err2 := New("err2")
err3 := err1.With(err2)
expect.True(t, err3.Is(err1))
expect.True(t, err3.Is(err2))
_ = err2.Subject("foo")
expect.True(t, err3.Is(err1))
expect.True(t, err3.Is(err2))
// check if err3 is affected by err2.Subject
expect.False(t, strings.Contains(err3.Error(), "foo"))
}
func TestErrorStringSimple(t *testing.T) {
errFailure := New("generic failure")
ne := errFailure.Subject("foo bar")
expect.Equal(t, ansi.StripANSI(ne.Error()), "foo bar: generic failure")
ne = ne.Subject("baz")
expect.Equal(t, ansi.StripANSI(ne.Error()), "baz > foo bar: generic failure")
}
func TestErrorStringNested(t *testing.T) {
errFailure := New("generic failure")
inner := errFailure.Subject("inner").
Withf("1").
Withf("1")
inner2 := errFailure.Subject("inner2").
Subject("action 2").
Withf("2").
Withf("2")
inner3 := errFailure.Subject("inner3").
Subject("action 3").
Withf("3").
Withf("3")
ne := errFailure.
Subject("foo").
Withf("bar").
Withf("baz").
With(inner).
With(inner.With(inner2.With(inner3)))
want := `foo: generic failure
• bar
• baz
• inner: generic failure
• 1
• 1
• inner: generic failure
• 1
• 1
• action 2 > inner2: generic failure
• 2
• 2
• action 3 > inner3: generic failure
• 3
• 3
`
expect.Equal(t, ansi.StripANSI(ne.Error()), want)
}

View File

@@ -1,43 +0,0 @@
package gperr
import "github.com/yusing/goutils/strings/ansi"
type Hint struct {
Prefix string
Message string
Suffix string
}
var _ PlainError = (*Hint)(nil)
var _ MarkdownError = (*Hint)(nil)
func (h *Hint) Error() string {
return h.Prefix + ansi.Info(h.Message) + h.Suffix
}
func (h *Hint) Plain() []byte {
return []byte(h.Prefix + h.Message + h.Suffix)
}
func (h *Hint) Markdown() []byte {
return []byte(h.Prefix + "**" + h.Message + "**" + h.Suffix)
}
func (h *Hint) MarshalText() ([]byte, error) {
return h.Plain(), nil
}
func (h *Hint) String() string {
return h.Error()
}
func DoYouMean(s string) error {
if s == "" {
return nil
}
return &Hint{
Prefix: "Do you mean ",
Message: s,
Suffix: "?",
}
}

View File

@@ -1,48 +0,0 @@
package gperr
import (
"os"
"github.com/rs/zerolog"
zerologlog "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common"
)
func log(msg string, err error, level zerolog.Level, logger ...*zerolog.Logger) {
var l *zerolog.Logger
if len(logger) > 0 {
l = logger[0]
} else {
l = &zerologlog.Logger
}
l.WithLevel(level).Msg(New(highlightANSI(msg)).With(err).Error())
switch level {
case zerolog.FatalLevel:
os.Exit(1)
case zerolog.PanicLevel:
panic(err)
}
}
func LogFatal(msg string, err error, logger ...*zerolog.Logger) {
if common.IsDebug {
LogPanic(msg, err, logger...)
}
log(msg, err, zerolog.FatalLevel, logger...)
}
func LogError(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.ErrorLevel, logger...)
}
func LogWarn(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.WarnLevel, logger...)
}
func LogPanic(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.PanicLevel, logger...)
}
func LogDebug(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.DebugLevel, logger...)
}

View File

@@ -1,55 +0,0 @@
package gperr
import (
"fmt"
"reflect"
)
type MultilineError struct {
nestedError
}
func Multiline() *MultilineError {
return &MultilineError{}
}
func (m *MultilineError) add(err error) {
if err == nil {
return
}
m.Extras = append(m.Extras, err)
}
func (m *MultilineError) Addf(format string, args ...any) *MultilineError {
m.add(fmt.Errorf(format, args...))
return m
}
func (m *MultilineError) Adds(s string) *MultilineError {
m.add(newError(s))
return m
}
func (m *MultilineError) AddLines(lines ...any) *MultilineError {
v := reflect.ValueOf(lines)
if v.Kind() == reflect.Slice {
for i := range v.Len() {
switch v := v.Index(i).Interface().(type) {
case string:
m.add(newError(v))
case error:
m.add(v)
default:
m.add(fmt.Errorf("%v", v))
}
}
}
return m
}
func (m *MultilineError) AddLinesString(lines ...string) *MultilineError {
for _, line := range lines {
m.add(newError(line))
}
return m
}

View File

@@ -1,29 +0,0 @@
package gperr
import (
"net"
"testing"
expect "github.com/yusing/godoxy/internal/utils/testing"
)
func TestWrapMultiline(t *testing.T) {
multiline := Multiline()
var wrapper error = wrap(multiline)
_, ok := wrapper.(*MultilineError)
if !ok {
t.Errorf("wrapper is not a MultilineError")
}
}
func TestPrependSubjectMultiline(t *testing.T) {
multiline := Multiline()
multiline.Addf("line 1 %s", "test")
multiline.Adds("line 2")
multiline.AddLines([]any{1, "2", 3.0, net.IPv4(127, 0, 0, 1)})
multiline.Subject("subject")
builder := NewBuilder()
builder.Add(multiline)
expect.Equal(t, len(multiline.Extras), len(builder.errs))
}

View File

@@ -1,152 +0,0 @@
package gperr
import (
"errors"
"fmt"
)
//nolint:recvcheck
type nestedError struct {
Err error `json:"err"`
Extras []error `json:"extras"`
}
var emptyError = errStr("")
func (err nestedError) Subject(subject string) Error {
if err.Err == nil {
err.Err = PrependSubject(subject, emptyError)
} else {
err.Err = PrependSubject(subject, err.Err)
}
return &err
}
func (err *nestedError) Subjectf(format string, args ...any) Error {
if len(args) > 0 {
return err.Subject(fmt.Sprintf(format, args...))
}
return err.Subject(format)
}
func (err nestedError) With(extra error) Error {
if extra != nil {
err.Extras = append(err.Extras, extra)
}
return &err
}
func (err nestedError) Withf(format string, args ...any) Error {
if len(args) > 0 {
err.Extras = append(err.Extras, fmt.Errorf(format, args...))
} else {
err.Extras = append(err.Extras, newError(format))
}
return &err
}
func (err *nestedError) Unwrap() []error {
if err.Err == nil {
if len(err.Extras) == 0 {
return nil
}
return err.Extras
}
return append([]error{err.Err}, err.Extras...)
}
func (err *nestedError) Is(other error) bool {
if errors.Is(err.Err, other) {
return true
}
for _, e := range err.Extras {
if errors.Is(e, other) {
return true
}
}
return false
}
var nilError = newError("<nil>")
var bulletPrefix = []byte("• ")
var markdownBulletPrefix = []byte("- ")
var spaces = []byte(" ")
type appendLineFunc func(buf []byte, err error, level int) []byte
func (err *nestedError) fmtError(appendLine appendLineFunc) []byte {
if err == nil {
return appendLine(nil, nilError, 0)
}
if err.Err != nil {
buf := appendLine(nil, err.Err, 0)
if len(err.Extras) > 0 {
buf = append(buf, '\n')
buf = appendLines(buf, err.Extras, 1, appendLine)
}
return buf
}
return appendLines(nil, err.Extras, 0, appendLine)
}
func (err *nestedError) Error() string {
return string(err.fmtError(appendLineNormal))
}
func (err *nestedError) Plain() []byte {
return err.fmtError(appendLinePlain)
}
func (err *nestedError) Markdown() []byte {
return err.fmtError(appendLineMd)
}
func appendLine(buf []byte, err error, level int, prefix []byte, format func(err error) []byte) []byte {
if err == nil {
return appendLine(buf, nilError, level, prefix, format)
}
if level == 0 {
return append(buf, format(err)...)
}
buf = append(buf, spaces[:2*level]...)
buf = append(buf, prefix...)
buf = append(buf, format(err)...)
return buf
}
func appendLineNormal(buf []byte, err error, level int) []byte {
return appendLine(buf, err, level, bulletPrefix, Normal)
}
func appendLinePlain(buf []byte, err error, level int) []byte {
return appendLine(buf, err, level, bulletPrefix, Plain)
}
func appendLineMd(buf []byte, err error, level int) []byte {
return appendLine(buf, err, level, markdownBulletPrefix, Markdown)
}
func appendLines(buf []byte, errs []error, level int, appendLine appendLineFunc) []byte {
if len(errs) == 0 {
return buf
}
for _, err := range errs {
switch err := wrap(err).(type) {
case *nestedError:
if err.Err != nil {
buf = appendLine(buf, err.Err, level)
buf = append(buf, '\n')
buf = appendLines(buf, err.Extras, level+1, appendLine)
} else {
buf = appendLines(buf, err.Extras, level, appendLine)
}
default:
if err == nil {
continue
}
buf = appendLine(buf, err, level)
buf = append(buf, '\n')
}
}
return buf
}

View File

@@ -1,144 +0,0 @@
package gperr
import (
"bytes"
"encoding/json"
"errors"
"slices"
"github.com/yusing/goutils/strings/ansi"
)
//nolint:errname
type withSubject struct {
Subjects []string
Err error
pendingSubject string
}
const subjectSep = " > "
type highlightFunc func(subject string) string
var _ PlainError = (*withSubject)(nil)
var _ MarkdownError = (*withSubject)(nil)
func highlightANSI(subject string) string {
return ansi.HighlightRed + subject + ansi.Reset
}
func highlightMarkdown(subject string) string {
return "**" + subject + "**"
}
func noHighlight(subject string) string {
return subject
}
func PrependSubject(subject string, err error) error {
if err == nil {
return nil
}
//nolint:errorlint
switch err := err.(type) {
case *withSubject:
return err.Prepend(subject)
case *wrappedError:
return &wrappedError{
Err: PrependSubject(subject, err.Err),
Message: err.Message,
}
case Error:
return err.Subject(subject)
}
return &withSubject{[]string{subject}, err, ""}
}
func (err *withSubject) Prepend(subject string) *withSubject {
if subject == "" {
return err
}
clone := *err
switch subject[0] {
case '[', '(', '{':
// since prepend is called in depth-first order,
// the subject of the index is not yet seen
// add it when the next subject is seen
clone.pendingSubject += subject
default:
clone.Subjects = append(clone.Subjects, subject)
if clone.pendingSubject != "" {
clone.Subjects[len(clone.Subjects)-1] = subject + clone.pendingSubject
clone.pendingSubject = ""
}
}
return &clone
}
func (err *withSubject) Is(other error) bool {
return errors.Is(other, err.Err)
}
func (err *withSubject) Unwrap() error {
return err.Err
}
func (err *withSubject) Error() string {
return string(err.fmtError(highlightANSI))
}
func (err *withSubject) Plain() []byte {
return err.fmtError(noHighlight)
}
func (err *withSubject) Markdown() []byte {
return err.fmtError(highlightMarkdown)
}
func (err *withSubject) fmtError(highlight highlightFunc) []byte {
// subject is in reversed order
size := 0
errStr := err.Err.Error()
subjects := err.Subjects
if err.pendingSubject != "" {
subjects = append(subjects, err.pendingSubject)
}
var buf bytes.Buffer
for _, s := range subjects {
size += len(s)
}
n := len(subjects)
buf.Grow(size + 2 + n*len(subjectSep) + len(errStr) + len(highlight("")))
for i := n - 1; i > 0; i-- {
buf.WriteString(subjects[i])
buf.WriteString(subjectSep)
}
buf.WriteString(highlight(subjects[0]))
if errStr != "" {
buf.WriteString(": ")
buf.WriteString(errStr)
}
return buf.Bytes()
}
// MarshalJSON implements the json.Marshaler interface.
func (err *withSubject) MarshalJSON() ([]byte, error) {
subjects := slices.Clone(err.Subjects)
slices.Reverse(subjects)
reversed := struct {
Subjects []string `json:"subjects"`
Err error `json:"err"`
}{
Subjects: subjects,
Err: err.Err,
}
if err.pendingSubject != "" {
reversed.Subjects = append(reversed.Subjects, err.pendingSubject)
}
return json.Marshal(reversed)
}

View File

@@ -1,133 +0,0 @@
package gperr
import (
"fmt"
)
func newError(message string) error {
return errStr(message)
}
func New(message string) Error {
if message == "" {
return nil
}
return &baseError{newError(message)}
}
func Errorf(format string, args ...any) Error {
return &baseError{fmt.Errorf(format, args...)}
}
// Wrap wraps message in front of the error message.
func Wrap(err error, message ...string) Error {
if err == nil {
return nil
}
if len(message) == 0 || message[0] == "" {
return wrap(err)
}
//nolint:errorlint
switch err := err.(type) {
case *baseError:
err.Err = &wrappedError{err.Err, message[0]}
return err
case *nestedError:
err.Err = &wrappedError{err.Err, message[0]}
return err
}
return &baseError{&wrappedError{err, message[0]}}
}
func Unwrap(err error) Error {
//nolint:errorlint
switch err := err.(type) {
case interface{ Unwrap() []error }:
return &nestedError{Extras: err.Unwrap()}
case interface{ Unwrap() error }:
return &baseError{err.Unwrap()}
default:
return &baseError{err}
}
}
func wrap(err error) Error {
if err == nil {
return nil
}
//nolint:errorlint
switch err := err.(type) {
case Error:
return err
}
return &baseError{err}
}
func Join(errors ...error) Error {
n := 0
for _, err := range errors {
if err != nil {
n++
}
}
if n == 0 {
return nil
}
errs := make([]error, n)
i := 0
for _, err := range errors {
if err != nil {
errs[i] = err
i++
}
}
return &nestedError{Extras: errs}
}
func JoinLines(main error, errors ...string) Error {
errs := make([]error, len(errors))
for i, err := range errors {
if err == "" {
continue
}
errs[i] = newError(err)
}
return &nestedError{Err: main, Extras: errs}
}
func Collect[T any, Err error, Arg any, Func func(Arg) (T, Err)](eb *Builder, fn Func, arg Arg) T {
result, err := fn(arg)
eb.Add(err)
return result
}
func Normal(err error) []byte {
if err == nil {
return nil
}
return []byte(err.Error())
}
func Plain(err error) []byte {
if err == nil {
return nil
}
if p, ok := err.(PlainError); ok {
return p.Plain()
}
return []byte(err.Error())
}
func Markdown(err error) []byte {
if err == nil {
return nil
}
switch err := err.(type) {
case MarkdownError:
return err.Markdown()
case interface{ Unwrap() []error }:
return appendLines(nil, err.Unwrap(), 0, appendLineMd)
default:
return []byte(err.Error())
}
}

View File

@@ -1,55 +0,0 @@
package gperr
import (
"testing"
)
type testErr struct{}
func (e testErr) Error() string {
return "test error"
}
func (e testErr) Plain() []byte {
return []byte("test error")
}
func (e testErr) Markdown() []byte {
return []byte("**test error**")
}
type testMultiErr struct {
errors []error
}
func (e testMultiErr) Error() string {
return Join(e.errors...).Error()
}
func (e testMultiErr) Unwrap() []error {
return e.errors
}
func TestFormatting(t *testing.T) {
err := testErr{}
plain := Plain(err)
if string(plain) != "test error" {
t.Errorf("expected test error, got %s", string(plain))
}
md := Markdown(err)
if string(md) != "**test error**" {
t.Errorf("expected test error, got %s", string(md))
}
}
func TestMultiError(t *testing.T) {
err := testMultiErr{[]error{testErr{}, testErr{}}}
plain := Plain(err)
if string(plain) != "test error\ntest error\n" {
t.Errorf("expected test error, got %s", string(plain))
}
md := Markdown(err)
if string(md) != "**test error**\n**test error**\n" {
t.Errorf("expected test error, got %s", string(md))
}
}

View File

@@ -1,34 +0,0 @@
package gperr
import (
"errors"
"fmt"
)
type wrappedError struct {
Err error
Message string
}
var _ PlainError = (*wrappedError)(nil)
var _ MarkdownError = (*wrappedError)(nil)
func (e *wrappedError) Error() string {
return fmt.Sprintf("%s: %s", e.Message, e.Err.Error())
}
func (e *wrappedError) Plain() []byte {
return fmt.Appendf(nil, "%s: %s", e.Message, e.Err.Error())
}
func (e *wrappedError) Markdown() []byte {
return fmt.Appendf(nil, "**%s**: %s", e.Message, e.Err.Error())
}
func (e *wrappedError) Unwrap() error {
return e.Err
}
func (e *wrappedError) Is(target error) bool {
return errors.Is(e.Err, target)
}

View File

@@ -4,7 +4,8 @@ import (
"testing" "testing"
. "github.com/yusing/godoxy/internal/homepage" . "github.com/yusing/godoxy/internal/homepage"
. "github.com/yusing/godoxy/internal/utils/testing"
expect "github.com/yusing/goutils/testing"
) )
func TestOverrideItem(t *testing.T) { func TestOverrideItem(t *testing.T) {
@@ -33,7 +34,7 @@ func TestOverrideItem(t *testing.T) {
overrides.Initialize() overrides.Initialize()
overrides.OverrideItem(a.Alias, want) overrides.OverrideItem(a.Alias, want)
got := a.GetOverride() got := a.GetOverride()
ExpectEqual(t, got, Item{ expect.Equal(t, got, Item{
ItemConfig: want, ItemConfig: want,
Alias: a.Alias, Alias: a.Alias,
}) })
@@ -58,8 +59,8 @@ func TestOverrideItem_PreservesURL(t *testing.T) {
overrides.OverrideItem(a.Alias, wantCfg) overrides.OverrideItem(a.Alias, wantCfg)
got := a.GetOverride() got := a.GetOverride()
ExpectEqual(t, got.URL, "http://origin.local") expect.Equal(t, got.URL, "http://origin.local")
ExpectEqual(t, got.Name, "Overridden") expect.Equal(t, got.Name, "Overridden")
} }
func TestVisibilityFavoriteAndSortOrders(t *testing.T) { func TestVisibilityFavoriteAndSortOrders(t *testing.T) {
@@ -81,11 +82,11 @@ func TestVisibilityFavoriteAndSortOrders(t *testing.T) {
overrides.SetFavSortOrder(a.Alias, 2) overrides.SetFavSortOrder(a.Alias, 2)
got := a.GetOverride() got := a.GetOverride()
ExpectEqual(t, got.Show, false) expect.Equal(t, got.Show, false)
ExpectEqual(t, got.Favorite, true) expect.Equal(t, got.Favorite, true)
ExpectEqual(t, got.SortOrder, 5) expect.Equal(t, got.SortOrder, 5)
ExpectEqual(t, got.AllSortOrder, 9) expect.Equal(t, got.AllSortOrder, 9)
ExpectEqual(t, got.FavSortOrder, 2) expect.Equal(t, got.FavSortOrder, 2)
} }
func TestCategoryDefaultedWhenEmpty(t *testing.T) { func TestCategoryDefaultedWhenEmpty(t *testing.T) {
@@ -97,7 +98,7 @@ func TestCategoryDefaultedWhenEmpty(t *testing.T) {
}, },
} }
got := a.GetOverride() got := a.GetOverride()
ExpectEqual(t, got.Category, CategoryOthers) expect.Equal(t, got.Category, CategoryOthers)
} }
func TestOverrideItems_Bulk(t *testing.T) { func TestOverrideItems_Bulk(t *testing.T) {
@@ -128,9 +129,9 @@ func TestOverrideItems_Bulk(t *testing.T) {
ga := a.GetOverride() ga := a.GetOverride()
gb := b.GetOverride() gb := b.GetOverride()
ExpectEqual(t, ga.Name, "A*") expect.Equal(t, ga.Name, "A*")
ExpectEqual(t, ga.Category, "AX") expect.Equal(t, ga.Category, "AX")
ExpectEqual(t, gb.Name, "B*") expect.Equal(t, gb.Name, "B*")
ExpectEqual(t, gb.Category, "BY") expect.Equal(t, gb.Category, "BY")
ExpectEqual(t, gb.Show, false) expect.Equal(t, gb.Show, false)
} }

View File

@@ -9,9 +9,9 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/jsonstore" "github.com/yusing/godoxy/internal/jsonstore"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
"github.com/yusing/godoxy/internal/utils/atomic" "github.com/yusing/godoxy/internal/utils/atomic"
"github.com/yusing/goutils/task"
) )
type cacheEntry struct { type cacheEntry struct {

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
) )
type ( type (

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
. "github.com/yusing/godoxy/internal/homepage" . "github.com/yusing/godoxy/internal/homepage"
expect "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
func strPtr(s string) *string { func strPtr(s string) *string {

View File

@@ -7,8 +7,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/homepage/widgets" "github.com/yusing/godoxy/internal/homepage/widgets"
gperr "github.com/yusing/goutils/errs"
) )
type Client struct { type Client struct {

View File

@@ -15,8 +15,8 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/task"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
) )
type ( type (

View File

@@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
) )
var HTTPClient = &http.Client{ var HTTPClient = &http.Client{

View File

@@ -3,8 +3,8 @@ package widgets
import ( import (
"context" "context"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
) )
type ( type (

View File

@@ -3,10 +3,10 @@ package idlewatcher
import ( import (
"time" "time"
"github.com/yusing/godoxy/internal/gperr"
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
// Start implements health.HealthMonitor. // Start implements health.HealthMonitor.

View File

@@ -5,10 +5,10 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/gperr"
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/godoxy/internal/watcher" "github.com/yusing/godoxy/internal/watcher"
gperr "github.com/yusing/goutils/errs"
) )
type DockerProvider struct { type DockerProvider struct {

View File

@@ -5,12 +5,12 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/yusing/godoxy/internal/gperr"
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
"github.com/yusing/godoxy/internal/proxmox" "github.com/yusing/godoxy/internal/proxmox"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/godoxy/internal/watcher" "github.com/yusing/godoxy/internal/watcher"
"github.com/yusing/godoxy/internal/watcher/events" "github.com/yusing/godoxy/internal/watcher/events"
gperr "github.com/yusing/goutils/errs"
) )
type ProxmoxProvider struct { type ProxmoxProvider struct {

View File

@@ -1,6 +1,6 @@
package idlewatcher package idlewatcher
import "github.com/yusing/godoxy/internal/gperr" import gperr "github.com/yusing/goutils/errs"
type ContainerStatus string type ContainerStatus string

View File

@@ -3,9 +3,9 @@ package idlewatcher
import ( import (
"context" "context"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/godoxy/internal/watcher/events" "github.com/yusing/godoxy/internal/watcher/events"
gperr "github.com/yusing/goutils/errs"
) )
type Provider interface { type Provider interface {

View File

@@ -12,18 +12,18 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/ds/ordered" "github.com/yusing/ds/ordered"
"github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/idlewatcher/provider" "github.com/yusing/godoxy/internal/idlewatcher/provider"
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types" idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
nettypes "github.com/yusing/godoxy/internal/net/types" nettypes "github.com/yusing/godoxy/internal/net/types"
"github.com/yusing/godoxy/internal/route/routes" "github.com/yusing/godoxy/internal/route/routes"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
U "github.com/yusing/godoxy/internal/utils" U "github.com/yusing/godoxy/internal/utils"
"github.com/yusing/godoxy/internal/utils/atomic" "github.com/yusing/godoxy/internal/utils/atomic"
"github.com/yusing/godoxy/internal/watcher/events" "github.com/yusing/godoxy/internal/watcher/events"
"github.com/yusing/godoxy/internal/watcher/health/monitor" "github.com/yusing/godoxy/internal/watcher/health/monitor"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/http/reverseproxy" "github.com/yusing/goutils/http/reverseproxy"
"github.com/yusing/goutils/task"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )

View File

@@ -10,9 +10,9 @@ import (
"github.com/puzpuzpuz/xsync/v4" "github.com/puzpuzpuz/xsync/v4"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/task" gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
type namespace string type namespace string

View File

@@ -9,12 +9,12 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
maxmind "github.com/yusing/godoxy/internal/maxmind/types" maxmind "github.com/yusing/godoxy/internal/maxmind/types"
"github.com/yusing/godoxy/internal/task" gperr "github.com/yusing/goutils/errs"
ioutils "github.com/yusing/goutils/io" ioutils "github.com/yusing/goutils/io"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/synk" "github.com/yusing/goutils/synk"
"github.com/yusing/goutils/task"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )

View File

@@ -9,9 +9,9 @@ import (
"time" "time"
. "github.com/yusing/godoxy/internal/logging/accesslog" . "github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
expect "github.com/yusing/godoxy/internal/utils/testing" "github.com/yusing/goutils/task"
expect "github.com/yusing/goutils/testing"
) )
const ( const (

View File

@@ -9,9 +9,10 @@ import (
"testing" "testing"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/yusing/godoxy/internal/task" expect "github.com/yusing/goutils/testing"
expect "github.com/yusing/godoxy/internal/utils/testing"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
) )
func TestBackScanner(t *testing.T) { func TestBackScanner(t *testing.T) {

View File

@@ -3,8 +3,8 @@ package accesslog
import ( import (
"time" "time"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
) )
type ( type (

View File

@@ -6,7 +6,7 @@ import (
"github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/docker"
. "github.com/yusing/godoxy/internal/logging/accesslog" . "github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
expect "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
func TestNewConfig(t *testing.T) { func TestNewConfig(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
. "github.com/yusing/godoxy/internal/logging/accesslog" . "github.com/yusing/godoxy/internal/logging/accesslog"
expect "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
// Cookie header should be removed, // Cookie header should be removed,

View File

@@ -6,9 +6,8 @@ import (
"sync" "sync"
"testing" "testing"
expect "github.com/yusing/godoxy/internal/utils/testing" "github.com/yusing/goutils/task"
expect "github.com/yusing/goutils/testing"
"github.com/yusing/godoxy/internal/task"
) )
func TestConcurrentFileLoggersShareSameAccessLogIO(t *testing.T) { func TestConcurrentFileLoggersShareSameAccessLogIO(t *testing.T) {

View File

@@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/yusing/godoxy/internal/gperr"
nettypes "github.com/yusing/godoxy/internal/net/types" nettypes "github.com/yusing/godoxy/internal/net/types"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
) )

View File

@@ -7,8 +7,8 @@ import (
. "github.com/yusing/godoxy/internal/logging/accesslog" . "github.com/yusing/godoxy/internal/logging/accesslog"
nettypes "github.com/yusing/godoxy/internal/net/types" nettypes "github.com/yusing/godoxy/internal/net/types"
expect "github.com/yusing/godoxy/internal/utils/testing"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
expect "github.com/yusing/goutils/testing"
) )
func TestStatusCodeFilter(t *testing.T) { func TestStatusCodeFilter(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
) )
@@ -70,7 +70,7 @@ func (r *Retention) Parse(v string) (err error) {
if !r.IsValid() { if !r.IsValid() {
return ErrZeroValue return ErrZeroValue
} }
return return err
} }
func (r *Retention) String() string { func (r *Retention) String() string {

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
. "github.com/yusing/godoxy/internal/logging/accesslog" . "github.com/yusing/godoxy/internal/logging/accesslog"
expect "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
func TestParseRetention(t *testing.T) { func TestParseRetention(t *testing.T) {

View File

@@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/synk" "github.com/yusing/goutils/synk"
) )
@@ -251,14 +251,14 @@ func rotateLogFileBySize(file supportRotate, config *Retention) (result *RotateR
// otherwise it returns zero time. // otherwise it returns zero time.
func ParseLogTime(line []byte) (t time.Time) { func ParseLogTime(line []byte) (t time.Time) {
if len(line) == 0 { if len(line) == 0 {
return return t
} }
if timeStr := ExtractTime(line); timeStr != nil { if timeStr := ExtractTime(line); timeStr != nil {
t, _ = time.Parse(LogTimeFormat, string(timeStr)) // ignore error t, _ = time.Parse(LogTimeFormat, string(timeStr)) // ignore error
return return t
} }
return return t
} }
var timeJSON = []byte(`"time":"`) var timeJSON = []byte(`"time":"`)
@@ -272,8 +272,8 @@ func ExtractTime(line []byte) []byte {
switch line[0] { switch line[0] {
case '{': // JSON format case '{': // JSON format
if i := bytes.Index(line, timeJSON); i != -1 { if i := bytes.Index(line, timeJSON); i != -1 {
var jsonStart = i + len(`"time":"`) jsonStart := i + len(`"time":"`)
var jsonEnd = i + len(`"time":"`) + len(LogTimeFormat) jsonEnd := i + len(`"time":"`) + len(LogTimeFormat)
if len(line) < jsonEnd { if len(line) < jsonEnd {
return nil return nil
} }

View File

@@ -7,10 +7,10 @@ import (
"time" "time"
. "github.com/yusing/godoxy/internal/logging/accesslog" . "github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/utils" "github.com/yusing/godoxy/internal/utils"
expect "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
) )
var ( var (

View File

@@ -3,7 +3,7 @@ package accesslog
import ( import (
"strconv" "strconv"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings" strutils "github.com/yusing/goutils/strings"
) )

View File

@@ -61,11 +61,11 @@ func (m *memLogger) Write(p []byte) (n int, err error) {
pos, err := m.writeBuf(p) pos, err := m.writeBuf(p)
if err != nil { if err != nil {
// not logging the error here, it will cause Run to be called again = infinite loop // not logging the error here, it will cause Run to be called again = infinite loop
return return n, err
} }
m.notifyWS(pos, n) m.notifyWS(pos, n)
return return n, err
} }
func (m *memLogger) ServeHTTP(c *gin.Context) { func (m *memLogger) ServeHTTP(c *gin.Context) {
@@ -149,7 +149,7 @@ func (m *memLogger) writeBuf(b []byte) (pos int, err error) {
defer m.Unlock() defer m.Unlock()
pos = m.Len() pos = m.Len()
_, err = m.Buffer.Write(b) _, err = m.Buffer.Write(b)
return return pos, err
} }
func (m *memLogger) events() (logs <-chan []byte, cancel func()) { func (m *memLogger) events() (logs <-chan []byte, cancel func()) {

View File

@@ -5,9 +5,9 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/notif"
"github.com/yusing/godoxy/internal/task" gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
var instance *MaxMind var instance *MaxMind

View File

@@ -15,9 +15,9 @@ import (
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
maxmind "github.com/yusing/godoxy/internal/maxmind/types" maxmind "github.com/yusing/godoxy/internal/maxmind/types"
"github.com/yusing/godoxy/internal/task" gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
/* /*

View File

@@ -10,7 +10,7 @@ import (
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
maxmind "github.com/yusing/godoxy/internal/maxmind/types" maxmind "github.com/yusing/godoxy/internal/maxmind/types"
"github.com/yusing/godoxy/internal/task" "github.com/yusing/goutils/task"
) )
func testCfg() *MaxMind { func testCfg() *MaxMind {

View File

@@ -3,7 +3,7 @@ package maxmind
import ( import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr" gperr "github.com/yusing/goutils/errs"
) )
type ( type (

View File

@@ -11,9 +11,9 @@ import (
"time" "time"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/utils/atomic" "github.com/yusing/godoxy/internal/utils/atomic"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
) )
type ( type (

View File

@@ -16,8 +16,8 @@ import (
"github.com/shirou/gopsutil/v4/sensors" "github.com/shirou/gopsutil/v4/sensors"
"github.com/shirou/gopsutil/v4/warning" "github.com/shirou/gopsutil/v4/warning"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/metrics/period" "github.com/yusing/godoxy/internal/metrics/period"
gperr "github.com/yusing/goutils/errs"
) )
// json tags are left for tests // json tags are left for tests

View File

@@ -10,96 +10,98 @@ import (
"github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/net" "github.com/shirou/gopsutil/v4/net"
"github.com/shirou/gopsutil/v4/sensors" "github.com/shirou/gopsutil/v4/sensors"
. "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
// Create test data // Create test data
var cpuAvg = 45.67 var (
var testInfo = &SystemInfo{ cpuAvg = 45.67
Timestamp: 123456, testInfo = &SystemInfo{
CPUAverage: &cpuAvg, Timestamp: 123456,
Memory: &mem.VirtualMemoryStat{ CPUAverage: &cpuAvg,
Total: 16000000000, Memory: &mem.VirtualMemoryStat{
Available: 8000000000, Total: 16000000000,
Used: 8000000000, Available: 8000000000,
UsedPercent: 50.0, Used: 8000000000,
},
Disks: map[string]*disk.UsageStat{
"sda": {
Path: "/",
Fstype: "ext4",
Total: 500000000000,
Free: 250000000000,
Used: 250000000000,
UsedPercent: 50.0, UsedPercent: 50.0,
}, },
"nvme0n1": { Disks: map[string]*disk.UsageStat{
Path: "/", "sda": {
Fstype: "zfs", Path: "/",
Total: 500000000000, Fstype: "ext4",
Free: 250000000000, Total: 500000000000,
Used: 250000000000, Free: 250000000000,
UsedPercent: 50.0, Used: 250000000000,
UsedPercent: 50.0,
},
"nvme0n1": {
Path: "/",
Fstype: "zfs",
Total: 500000000000,
Free: 250000000000,
Used: 250000000000,
UsedPercent: 50.0,
},
}, },
}, DisksIO: map[string]*disk.IOCountersStat{
DisksIO: map[string]*disk.IOCountersStat{ "media": {
"media": { Name: "media",
Name: "media", ReadBytes: 1000000,
ReadBytes: 1000000, WriteBytes: 2000000,
WriteBytes: 2000000, ReadSpeed: 100.5,
ReadSpeed: 100.5, WriteSpeed: 200.5,
WriteSpeed: 200.5, Iops: 1000,
Iops: 1000, },
"nvme0n1": {
Name: "nvme0n1",
ReadBytes: 1000000,
WriteBytes: 2000000,
ReadSpeed: 100.5,
WriteSpeed: 200.5,
Iops: 1000,
},
}, },
"nvme0n1": { Network: &net.IOCountersStat{
Name: "nvme0n1", BytesSent: 5000000,
ReadBytes: 1000000, BytesRecv: 10000000,
WriteBytes: 2000000, UploadSpeed: 1024.5,
ReadSpeed: 100.5, DownloadSpeed: 2048.5,
WriteSpeed: 200.5,
Iops: 1000,
}, },
}, Sensors: []sensors.TemperatureStat{
Network: &net.IOCountersStat{ {
BytesSent: 5000000, SensorKey: "cpu_temp",
BytesRecv: 10000000, Temperature: 30.0,
UploadSpeed: 1024.5, High: 40.0,
DownloadSpeed: 2048.5, Critical: 50.0,
}, },
Sensors: []sensors.TemperatureStat{ {
{ SensorKey: "gpu_temp",
SensorKey: "cpu_temp", Temperature: 40.0,
Temperature: 30.0, High: 50.0,
High: 40.0, Critical: 60.0,
Critical: 50.0, },
}, },
{ }
SensorKey: "gpu_temp", )
Temperature: 40.0,
High: 50.0,
Critical: 60.0,
},
},
}
func TestSystemInfo(t *testing.T) { func TestSystemInfo(t *testing.T) {
// Test marshaling // Test marshaling
data, err := json.Marshal(testInfo) data, err := json.Marshal(testInfo)
ExpectNoError(t, err) expect.NoError(t, err)
// Test unmarshaling back // Test unmarshaling back
var decoded SystemInfo var decoded SystemInfo
err = json.Unmarshal(data, &decoded) err = json.Unmarshal(data, &decoded)
ExpectNoError(t, err) expect.NoError(t, err)
// Compare original and decoded // Compare original and decoded
ExpectEqual(t, decoded.Timestamp, testInfo.Timestamp) expect.Equal(t, decoded.Timestamp, testInfo.Timestamp)
ExpectEqual(t, *decoded.CPUAverage, *testInfo.CPUAverage) expect.Equal(t, *decoded.CPUAverage, *testInfo.CPUAverage)
ExpectEqual(t, decoded.Memory, testInfo.Memory) expect.Equal(t, decoded.Memory, testInfo.Memory)
ExpectEqual(t, decoded.Disks, testInfo.Disks) expect.Equal(t, decoded.Disks, testInfo.Disks)
ExpectEqual(t, decoded.DisksIO, testInfo.DisksIO) expect.Equal(t, decoded.DisksIO, testInfo.DisksIO)
ExpectEqual(t, decoded.Network, testInfo.Network) expect.Equal(t, decoded.Network, testInfo.Network)
ExpectEqual(t, decoded.Sensors, testInfo.Sensors) expect.Equal(t, decoded.Sensors, testInfo.Sensors)
// Test nil fields // Test nil fields
nilInfo := &SystemInfo{ nilInfo := &SystemInfo{
@@ -107,18 +109,18 @@ func TestSystemInfo(t *testing.T) {
} }
data, err = json.Marshal(nilInfo) data, err = json.Marshal(nilInfo)
ExpectNoError(t, err) expect.NoError(t, err)
var decodedNil SystemInfo var decodedNil SystemInfo
err = json.Unmarshal(data, &decodedNil) err = json.Unmarshal(data, &decodedNil)
ExpectNoError(t, err) expect.NoError(t, err)
ExpectEqual(t, decodedNil.Timestamp, nilInfo.Timestamp) expect.Equal(t, decodedNil.Timestamp, nilInfo.Timestamp)
ExpectTrue(t, decodedNil.CPUAverage == nil) expect.True(t, decodedNil.CPUAverage == nil)
ExpectTrue(t, decodedNil.Memory == nil) expect.True(t, decodedNil.Memory == nil)
ExpectTrue(t, decodedNil.Disks == nil) expect.True(t, decodedNil.Disks == nil)
ExpectTrue(t, decodedNil.Network == nil) expect.True(t, decodedNil.Network == nil)
ExpectTrue(t, decodedNil.Sensors == nil) expect.True(t, decodedNil.Sensors == nil)
} }
func TestSerialize(t *testing.T) { func TestSerialize(t *testing.T) {
@@ -130,15 +132,15 @@ func TestSerialize(t *testing.T) {
t.Run(string(query), func(t *testing.T) { t.Run(string(query), func(t *testing.T) {
_, result := aggregate(entries, url.Values{"aggregate": []string{string(query)}}) _, result := aggregate(entries, url.Values{"aggregate": []string{string(query)}})
s, err := result.MarshalJSON() s, err := result.MarshalJSON()
ExpectNoError(t, err) expect.NoError(t, err)
var v []map[string]any var v []map[string]any
ExpectNoError(t, json.Unmarshal(s, &v)) expect.NoError(t, json.Unmarshal(s, &v))
ExpectEqual(t, len(v), len(result.Entries)) expect.Equal(t, len(v), len(result.Entries))
for i, m := range v { for i, m := range v {
for k, v := range m { for k, v := range m {
// some int64 values are converted to float64 on json.Unmarshal // some int64 values are converted to float64 on json.Unmarshal
vv := reflect.ValueOf(result.Entries[i][k]) vv := reflect.ValueOf(result.Entries[i][k])
ExpectEqual(t, reflect.ValueOf(v).Convert(vv.Type()).Interface(), vv.Interface()) expect.Equal(t, reflect.ValueOf(v).Convert(vv.Type()).Interface(), vv.Interface())
} }
} }
}) })

View File

@@ -4,9 +4,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net/url" "net/url"
"time"
"slices" "slices"
"time"
"github.com/lithammer/fuzzysearch/fuzzy" "github.com/lithammer/fuzzysearch/fuzzy"
config "github.com/yusing/godoxy/internal/config/types" config "github.com/yusing/godoxy/internal/config/types"

View File

@@ -4,38 +4,38 @@ import (
"net/http" "net/http"
"testing" "testing"
. "github.com/yusing/godoxy/internal/utils/testing" expect "github.com/yusing/goutils/testing"
) )
func TestContentTypes(t *testing.T) { func TestContentTypes(t *testing.T) {
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"text/html"}}).IsHTML()) expect.True(t, GetContentType(http.Header{"Content-Type": {"text/html"}}).IsHTML())
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"text/html; charset=utf-8"}}).IsHTML()) expect.True(t, GetContentType(http.Header{"Content-Type": {"text/html; charset=utf-8"}}).IsHTML())
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"application/xhtml+xml"}}).IsHTML()) expect.True(t, GetContentType(http.Header{"Content-Type": {"application/xhtml+xml"}}).IsHTML())
ExpectFalse(t, GetContentType(http.Header{"Content-Type": {"text/plain"}}).IsHTML()) expect.False(t, GetContentType(http.Header{"Content-Type": {"text/plain"}}).IsHTML())
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"application/json"}}).IsJSON()) expect.True(t, GetContentType(http.Header{"Content-Type": {"application/json"}}).IsJSON())
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"application/json; charset=utf-8"}}).IsJSON()) expect.True(t, GetContentType(http.Header{"Content-Type": {"application/json; charset=utf-8"}}).IsJSON())
ExpectFalse(t, GetContentType(http.Header{"Content-Type": {"text/html"}}).IsJSON()) expect.False(t, GetContentType(http.Header{"Content-Type": {"text/html"}}).IsJSON())
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"text/plain"}}).IsPlainText()) expect.True(t, GetContentType(http.Header{"Content-Type": {"text/plain"}}).IsPlainText())
ExpectTrue(t, GetContentType(http.Header{"Content-Type": {"text/plain; charset=utf-8"}}).IsPlainText()) expect.True(t, GetContentType(http.Header{"Content-Type": {"text/plain; charset=utf-8"}}).IsPlainText())
ExpectFalse(t, GetContentType(http.Header{"Content-Type": {"text/html"}}).IsPlainText()) expect.False(t, GetContentType(http.Header{"Content-Type": {"text/html"}}).IsPlainText())
} }
func TestAcceptContentTypes(t *testing.T) { func TestAcceptContentTypes(t *testing.T) {
ExpectTrue(t, GetAccept(http.Header{"Accept": {"text/html", "text/plain"}}).AcceptPlainText()) expect.True(t, GetAccept(http.Header{"Accept": {"text/html", "text/plain"}}).AcceptPlainText())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"text/html", "text/plain; charset=utf-8"}}).AcceptPlainText()) expect.True(t, GetAccept(http.Header{"Accept": {"text/html", "text/plain; charset=utf-8"}}).AcceptPlainText())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"text/html", "text/plain"}}).AcceptHTML()) expect.True(t, GetAccept(http.Header{"Accept": {"text/html", "text/plain"}}).AcceptHTML())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"application/json"}}).AcceptJSON()) expect.True(t, GetAccept(http.Header{"Accept": {"application/json"}}).AcceptJSON())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"*/*"}}).AcceptPlainText()) expect.True(t, GetAccept(http.Header{"Accept": {"*/*"}}).AcceptPlainText())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"*/*"}}).AcceptHTML()) expect.True(t, GetAccept(http.Header{"Accept": {"*/*"}}).AcceptHTML())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"*/*"}}).AcceptJSON()) expect.True(t, GetAccept(http.Header{"Accept": {"*/*"}}).AcceptJSON())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"text/*"}}).AcceptPlainText()) expect.True(t, GetAccept(http.Header{"Accept": {"text/*"}}).AcceptPlainText())
ExpectTrue(t, GetAccept(http.Header{"Accept": {"text/*"}}).AcceptHTML()) expect.True(t, GetAccept(http.Header{"Accept": {"text/*"}}).AcceptHTML())
ExpectFalse(t, GetAccept(http.Header{"Accept": {"text/plain"}}).AcceptHTML()) expect.False(t, GetAccept(http.Header{"Accept": {"text/plain"}}).AcceptHTML())
ExpectFalse(t, GetAccept(http.Header{"Accept": {"text/plain; charset=utf-8"}}).AcceptHTML()) expect.False(t, GetAccept(http.Header{"Accept": {"text/plain; charset=utf-8"}}).AcceptHTML())
ExpectFalse(t, GetAccept(http.Header{"Accept": {"text/html"}}).AcceptPlainText()) expect.False(t, GetAccept(http.Header{"Accept": {"text/html"}}).AcceptPlainText())
ExpectFalse(t, GetAccept(http.Header{"Accept": {"text/html"}}).AcceptJSON()) expect.False(t, GetAccept(http.Header{"Accept": {"text/html"}}).AcceptJSON())
ExpectFalse(t, GetAccept(http.Header{"Accept": {"text/*"}}).AcceptJSON()) expect.False(t, GetAccept(http.Header{"Accept": {"text/*"}}).AcceptJSON())
} }

View File

@@ -6,9 +6,9 @@ import (
"net/http" "net/http"
"sync" "sync"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/net/gphttp/middleware"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
gperr "github.com/yusing/goutils/errs"
) )
type ipHash struct { type ipHash struct {

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