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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,8 +8,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/certs"
. "github.com/yusing/godoxy/internal/api/types"
config "github.com/yusing/godoxy/internal/config/types"
apitypes "github.com/yusing/goutils/apitypes"
)
type VerifyNewAgentRequest struct {
@@ -35,44 +35,44 @@ type VerifyNewAgentRequest struct {
func Verify(c *gin.Context) {
var request VerifyNewAgentRequest
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
}
filename, ok := certs.AgentCertsFilepath(request.Host)
if !ok {
c.JSON(http.StatusBadRequest, Error("invalid host", nil))
c.JSON(http.StatusBadRequest, apitypes.Error("invalid host", nil))
return
}
ca, err := fromEncryptedPEMPairResponse(request.CA)
if err != nil {
c.JSON(http.StatusBadRequest, Error("invalid CA", err))
c.JSON(http.StatusBadRequest, apitypes.Error("invalid CA", err))
return
}
client, err := fromEncryptedPEMPairResponse(request.Client)
if err != nil {
c.JSON(http.StatusBadRequest, Error("invalid client", err))
c.JSON(http.StatusBadRequest, apitypes.Error("invalid client", err))
return
}
nRoutesAdded, err := config.GetInstance().VerifyNewAgent(request.Host, ca, client, request.ContainerRuntime)
if err != nil {
c.JSON(http.StatusBadRequest, Error("invalid request", err))
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return
}
zip, err := certs.ZipCert(ca.Cert, client.Cert, client.Key)
if err != nil {
c.Error(InternalServerError(err, "failed to zip certs"))
c.Error(apitypes.InternalServerError(err, "failed to zip certs"))
return
}
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
}
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"
apitypes "github.com/yusing/godoxy/internal/api/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/net/gphttp/websocket"
gperr "github.com/yusing/goutils/errs"
)
// @x-id "renew"

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,9 +6,9 @@ import (
"github.com/gin-gonic/gin"
apitypes "github.com/yusing/godoxy/internal/api/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/route/provider"
gperr "github.com/yusing/goutils/errs"
)
type ValidateFileRequest struct {

View File

@@ -14,10 +14,10 @@ import (
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/agent/pkg/agent"
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/systeminfo"
"github.com/yusing/godoxy/internal/net/gphttp/websocket"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/http/httpheaders"
"github.com/yusing/goutils/synk"
)

View File

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

View File

@@ -16,7 +16,7 @@ import (
"github.com/yusing/godoxy/internal/common"
"golang.org/x/oauth2"
. "github.com/yusing/godoxy/internal/utils/testing"
expect "github.com/yusing/goutils/testing"
)
// setupMockOIDC configures mock OIDC provider for testing.
@@ -35,7 +35,7 @@ func setupMockOIDC(t *testing.T) {
},
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,
oidcVerifier: provider.Verifier(&oidc.Config{
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.Header["kid"] = keyID
signed, err := token.SignedString(j.key)
ExpectNoError(t, err)
expect.NoError(t, err)
return signed
}
@@ -84,7 +84,7 @@ func setupProvider(t *testing.T) *provider {
// Generate an RSA key pair for the test.
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.
jwk := buildRSAJWK(t, &privKey.PublicKey, keyID)
@@ -227,12 +227,12 @@ func TestOIDCCallbackHandler(t *testing.T) {
}
if tt.wantStatus == http.StatusTemporaryRedirect {
setCookie := Must(http.ParseSetCookie(w.Header().Get("Set-Cookie")))
ExpectEqual(t, setCookie.Name, CookieOauthToken)
ExpectTrue(t, setCookie.Value != "")
ExpectEqual(t, setCookie.Path, "/")
ExpectEqual(t, setCookie.SameSite, http.SameSiteLaxMode)
ExpectEqual(t, setCookie.HttpOnly, true)
setCookie := expect.Must(http.ParseSetCookie(w.Header().Get("Set-Cookie")))
expect.Equal(t, setCookie.Name, CookieOauthToken)
expect.True(t, setCookie.Value != "")
expect.Equal(t, setCookie.Path, "/")
expect.Equal(t, setCookie.SameSite, http.SameSiteLaxMode)
expect.Equal(t, setCookie.HttpOnly, true)
}
})
}
@@ -245,7 +245,7 @@ func TestInitOIDC(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
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)
t.Cleanup(server.Close)
@@ -445,9 +445,9 @@ func TestCheckToken(t *testing.T) {
// Call CheckToken and verify the result.
err := auth.CheckToken(req)
if tc.wantErr == nil {
ExpectNoError(t, err)
expect.NoError(t, err)
} 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/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/net/gphttp"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings"
"golang.org/x/crypto/bcrypt"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@ package config
import (
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/gperr"
"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) {

View File

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

View File

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

View File

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

View File

@@ -148,7 +148,7 @@ require (
github.com/vultr/govultr/v3 v3.24.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // 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/contrib/instrumentation/net/http/otelhttp v0.63.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.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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.2.1/go.mod h1:v6RZsMRdzcts4udSg0vqUIFvaD0OaUMPTwYJZ4XnQYo=
github.com/yusing/goutils v0.3.1 h1:xCPoZ/haI8ZJ0ZaPU4g6+okSPdBczs8o98tIZ/TbpsQ=
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/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
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/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/goutils/task"
)
// TODO: implement reconnect here.

View File

@@ -14,10 +14,10 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/godoxy/internal/utils"
gperr "github.com/yusing/goutils/errs"
)
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 {
addError(res, ErrNoNetwork)
}
return
return res
}
func IsBlacklisted(c *types.Container) bool {

View File

@@ -4,7 +4,7 @@ import (
"testing"
"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) {
@@ -37,7 +37,7 @@ func TestContainerExplicit(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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 {
t.Run(tt.name, func(t *testing.T) {
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) {
helper := containerHelper{&container.Summary{Image: tt.full}}
im := helper.parseImage()
ExpectEqual(t, im.Author, tt.author)
ExpectEqual(t, im.Name, tt.image)
ExpectEqual(t, im.Tag, tt.tag)
expect.Equal(t, im.Author, tt.author)
expect.Equal(t, im.Name, tt.image)
expect.Equal(t, im.Tag, tt.tag)
})
}
}

View File

@@ -5,8 +5,8 @@ import (
"strings"
"github.com/goccy/go-yaml"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/types"
gperr "github.com/yusing/goutils/errs"
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/errorpage"
"github.com/yusing/godoxy/internal/route/routes"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
)
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) {
if cfg == nil {
ep.accessLogger = nil
return
return err
}
ep.accessLogger, err = accesslog.NewAccessLogger(parent, cfg)
if err != nil {
return
return err
}
log.Debug().Msg("entrypoint access logger created")
return
return err
}
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/routes"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
)
type noopResponseWriter struct {
@@ -24,10 +24,12 @@ type noopResponseWriter struct {
func (w *noopResponseWriter) Header() http.Header {
return http.Header{}
}
func (w *noopResponseWriter) Write(b []byte) (int, error) {
w.written = b
return len(b), nil
}
func (w *noopResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}
@@ -45,7 +47,7 @@ func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
func BenchmarkEntrypointReal(b *testing.B) {
var ep Entrypoint
var req = http.Request{
req := http.Request{
Method: "GET",
URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld",
@@ -107,7 +109,7 @@ func BenchmarkEntrypointReal(b *testing.B) {
func BenchmarkEntrypoint(b *testing.B) {
var ep Entrypoint
var req = http.Request{
req := http.Request{
Method: "GET",
URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld",

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import (
"testing"
. "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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,9 +10,9 @@ import (
"github.com/puzpuzpuz/xsync/v4"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"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

View File

@@ -9,12 +9,12 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
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"
strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/synk"
"github.com/yusing/goutils/task"
"golang.org/x/time/rate"
)

View File

@@ -9,9 +9,9 @@ import (
"time"
. "github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/task"
"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 (

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import (
"github.com/yusing/godoxy/internal/docker"
. "github.com/yusing/godoxy/internal/logging/accesslog"
"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) {

View File

@@ -4,7 +4,7 @@ import (
"testing"
. "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,

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import (
"testing"
. "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) {

View File

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

View File

@@ -7,10 +7,10 @@ import (
"time"
. "github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/task"
"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"
"github.com/yusing/goutils/task"
)
var (

View File

@@ -3,7 +3,7 @@ package accesslog
import (
"strconv"
"github.com/yusing/godoxy/internal/gperr"
gperr "github.com/yusing/goutils/errs"
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)
if err != nil {
// not logging the error here, it will cause Run to be called again = infinite loop
return
return n, err
}
m.notifyWS(pos, n)
return
return n, err
}
func (m *memLogger) ServeHTTP(c *gin.Context) {
@@ -149,7 +149,7 @@ func (m *memLogger) writeBuf(b []byte) (pos int, err error) {
defer m.Unlock()
pos = m.Len()
_, err = m.Buffer.Write(b)
return
return pos, err
}
func (m *memLogger) events() (logs <-chan []byte, cancel func()) {

View File

@@ -5,9 +5,9 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
"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

View File

@@ -15,9 +15,9 @@ import (
"github.com/oschwald/maxminddb-golang"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
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"
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/goutils/task"
)
func testCfg() *MaxMind {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,11 +8,11 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/godoxy/internal/utils/pool"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/http/httpheaders"
"github.com/yusing/goutils/task"
)
// TODO: stats of each server.

View File

@@ -4,7 +4,7 @@ import (
"testing"
"github.com/yusing/godoxy/internal/types"
. "github.com/yusing/godoxy/internal/utils/testing"
expect "github.com/yusing/goutils/testing"
)
func TestRebalance(t *testing.T) {
@@ -15,7 +15,7 @@ func TestRebalance(t *testing.T) {
lb.AddServer(TestNewServer(0))
}
lb.rebalance()
ExpectEqual(t, lb.sumWeight, maxWeight)
expect.Equal(t, lb.sumWeight, maxWeight)
})
t.Run("less", func(t *testing.T) {
lb := New(new(types.LoadBalancerConfig))
@@ -26,7 +26,7 @@ func TestRebalance(t *testing.T) {
lb.AddServer(TestNewServer(float64(maxWeight) * .1))
lb.rebalance()
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
ExpectEqual(t, lb.sumWeight, maxWeight)
expect.Equal(t, lb.sumWeight, maxWeight)
})
t.Run("more", func(t *testing.T) {
lb := New(new(types.LoadBalancerConfig))
@@ -39,6 +39,6 @@ func TestRebalance(t *testing.T) {
lb.AddServer(TestNewServer(float64(maxWeight) * .1))
lb.rebalance()
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
ExpectEqual(t, lb.sumWeight, maxWeight)
expect.Equal(t, lb.sumWeight, maxWeight)
})
}

View File

@@ -14,9 +14,9 @@ import (
. "github.com/yusing/godoxy/internal/net/gphttp/middleware"
"github.com/yusing/godoxy/internal/route"
routeTypes "github.com/yusing/godoxy/internal/route/types"
"github.com/yusing/godoxy/internal/task"
expect "github.com/yusing/godoxy/internal/utils/testing"
"github.com/yusing/goutils/http/reverseproxy"
"github.com/yusing/goutils/task"
expect "github.com/yusing/goutils/testing"
)
func noOpHandler(w http.ResponseWriter, r *http.Request) {}

View File

@@ -12,7 +12,7 @@ import (
_ "embed"
"github.com/yusing/godoxy/internal/gperr"
gperr "github.com/yusing/goutils/errs"
)
type HcaptchaProvider struct {

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"time"
"github.com/yusing/godoxy/internal/gperr"
gperr "github.com/yusing/goutils/errs"
)
type Provider interface {
@@ -16,6 +16,4 @@ type Provider interface {
FormHTML() string
}
var (
ErrCaptchaVerificationFailed = gperr.New("captcha verification failed")
)
var ErrCaptchaVerificationFailed = gperr.New("captcha verification failed")

View File

@@ -7,9 +7,9 @@ import (
"strings"
"testing"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/serialization"
. "github.com/yusing/godoxy/internal/utils/testing"
gperr "github.com/yusing/goutils/errs"
expect "github.com/yusing/goutils/testing"
)
//go:embed test_data/cidr_whitelist_test.yml
@@ -23,32 +23,32 @@ func TestCIDRWhitelistValidation(t *testing.T) {
"allow": []string{"192.168.2.100/32"},
"message": testMessage,
})
ExpectNoError(t, err)
expect.NoError(t, err)
_, err = CIDRWhiteList.New(OptionsRaw{
"allow": []string{"192.168.2.100/32"},
"message": testMessage,
"status": 403,
})
ExpectNoError(t, err)
expect.NoError(t, err)
_, err = CIDRWhiteList.New(OptionsRaw{
"allow": []string{"192.168.2.100/32"},
"message": testMessage,
"status_code": 403,
})
ExpectNoError(t, err)
expect.NoError(t, err)
})
t.Run("missing allow", func(t *testing.T) {
_, err := CIDRWhiteList.New(OptionsRaw{
"message": testMessage,
})
ExpectError(t, serialization.ErrValidationError, err)
expect.ErrorIs(t, serialization.ErrValidationError, err)
})
t.Run("invalid cidr", func(t *testing.T) {
_, err := CIDRWhiteList.New(OptionsRaw{
"allow": []string{"192.168.2.100/123"},
"message": testMessage,
})
ExpectErrorT[*net.ParseError](t, err)
expect.ErrorT[*net.ParseError](t, err)
})
t.Run("invalid status code", func(t *testing.T) {
_, err := CIDRWhiteList.New(OptionsRaw{
@@ -56,14 +56,14 @@ func TestCIDRWhitelistValidation(t *testing.T) {
"status_code": 600,
"message": testMessage,
})
ExpectError(t, serialization.ErrValidationError, err)
expect.ErrorIs(t, serialization.ErrValidationError, err)
})
}
func TestCIDRWhitelist(t *testing.T) {
errs := gperr.NewBuilder("")
mids := BuildMiddlewaresFromYAML("", testCIDRWhitelistCompose, errs)
ExpectNoError(t, errs.Error())
expect.NoError(t, errs.Error())
deny = mids["deny@file"]
accept = mids["accept@file"]
if deny == nil || accept == nil {
@@ -74,9 +74,9 @@ func TestCIDRWhitelist(t *testing.T) {
t.Parallel()
for range 10 {
result, err := newMiddlewareTest(deny, nil)
ExpectNoError(t, err)
ExpectEqual(t, result.ResponseStatus, cidrWhitelistDefaults.StatusCode)
ExpectEqual(t, strings.TrimSpace(string(result.Data)), cidrWhitelistDefaults.Message)
expect.NoError(t, err)
expect.Equal(t, result.ResponseStatus, cidrWhitelistDefaults.StatusCode)
expect.Equal(t, strings.TrimSpace(string(result.Data)), cidrWhitelistDefaults.Message)
}
})
@@ -84,8 +84,8 @@ func TestCIDRWhitelist(t *testing.T) {
t.Parallel()
for range 10 {
result, err := newMiddlewareTest(accept, nil)
ExpectNoError(t, err)
ExpectEqual(t, result.ResponseStatus, http.StatusOK)
expect.NoError(t, err)
expect.Equal(t, result.ResponseStatus, http.StatusOK)
}
})
}

View File

@@ -63,14 +63,14 @@ func (cri *cloudflareRealIP) before(w http.ResponseWriter, r *http.Request) bool
func tryFetchCFCIDR() (cfCIDRs []*nettypes.CIDR) {
if time.Since(cfCIDRsLastUpdate.Load()) < cfCIDRsUpdateInterval {
return
return cfCIDRs
}
cfCIDRsMu.Lock()
defer cfCIDRsMu.Unlock()
if time.Since(cfCIDRsLastUpdate.Load()) < cfCIDRsUpdateInterval {
return
return cfCIDRs
}
if common.IsTest {
@@ -93,7 +93,7 @@ func tryFetchCFCIDR() (cfCIDRs []*nettypes.CIDR) {
cfCIDRsLastUpdate.Store(time.Now())
log.Info().Msg("cloudflare CIDR range updated")
return
return cfCIDRs
}
func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*nettypes.CIDR) error {

View File

@@ -9,11 +9,11 @@ import (
"github.com/puzpuzpuz/xsync/v4"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/gperr"
"github.com/yusing/godoxy/internal/task"
"github.com/yusing/godoxy/internal/utils"
"github.com/yusing/godoxy/internal/watcher"
"github.com/yusing/godoxy/internal/watcher/events"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/task"
)
const errPagesBasePath = common.ErrorPagesBasePath
@@ -42,7 +42,7 @@ func GetErrorPageByStatus(statusCode int) (content []byte, ok bool) {
if !ok && statusCode != 404 {
return fileContentMap.Load("404.html")
}
return
return content, ok
}
func loadContent() {

View File

@@ -10,9 +10,9 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/gperr"
gphttp "github.com/yusing/godoxy/internal/net/gphttp"
"github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/http/reverseproxy"
)
@@ -207,10 +207,10 @@ func PatchReverseProxy(rp *ReverseProxy, middlewaresMap map[string]OptionsRaw) (
var middlewares []*Middleware
middlewares, err = compileMiddlewares(middlewaresMap)
if err != nil {
return
return err
}
patchReverseProxy(rp, middlewares)
return
return err
}
func patchReverseProxy(rp *ReverseProxy, middlewares []*Middleware) {

View File

@@ -7,7 +7,7 @@ import (
"sort"
"github.com/goccy/go-yaml"
"github.com/yusing/godoxy/internal/gperr"
gperr "github.com/yusing/goutils/errs"
)
var ErrMissingMiddlewareUse = gperr.New("missing middleware 'use' field")

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