refactor: replace gperr with standard errors package and simplify string parsing

- Replace gperr.Error return types with standard error across test files
- Replace gperr.New with errors.New in validation and serialization tests
- Update API documentation in README files to use error instead of gperr.Error
- Simplify string parsing using strings.Cut in docker/label.go
- Update benchmarks to use NewTestEntrypoint and remove task package dependency
This commit is contained in:
yusing
2026-02-10 16:59:19 +08:00
parent a0d0ad0958
commit 1579f490c0
11 changed files with 45 additions and 69 deletions

View File

@@ -54,7 +54,7 @@ func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, e
// Move deeper into the nested map // Move deeper into the nested map
m, ok := currentMap[k].(types.LabelMap) m, ok := currentMap[k].(types.LabelMap)
if !ok && currentMap[k] != "" { if !ok && currentMap[k] != "" {
errs.Add(gperr.Errorf("expect mapping, got %T", currentMap[k]).Subject(lbl)) errs.AddSubject(fmt.Errorf("expect mapping, got %T", currentMap[k]), lbl)
continue continue
} else if !ok { } else if !ok {
m = make(types.LabelMap) m = make(types.LabelMap)
@@ -83,15 +83,7 @@ func ExpandWildcard(labels map[string]string, aliases ...string) {
} }
// lbl is "proxy.X..." where X is alias or wildcard // lbl is "proxy.X..." where X is alias or wildcard
rest := lbl[len(nsProxyDot):] // "X..." or "X.suffix" rest := lbl[len(nsProxyDot):] // "X..." or "X.suffix"
dotIdx := strings.IndexByte(rest, '.') alias, suffix, _ := strings.Cut(rest, ".")
var alias, suffix string
if dotIdx == -1 {
alias = rest
} else {
alias = rest[:dotIdx]
suffix = rest[dotIdx+1:]
}
if alias == WildcardAlias { if alias == WildcardAlias {
delete(labels, lbl) delete(labels, lbl)
if suffix == "" || strings.Count(value, "\n") > 1 { if suffix == "" || strings.Count(value, "\n") > 1 {
@@ -121,15 +113,10 @@ func ExpandWildcard(labels map[string]string, aliases ...string) {
continue continue
} }
rest := lbl[len(nsProxyDot):] rest := lbl[len(nsProxyDot):]
dotIdx := strings.IndexByte(rest, '.') alias, suffix, ok := strings.Cut(rest, ".")
if dotIdx == -1 { if !ok || alias == "" || alias[0] == '#' {
continue continue
} }
alias := rest[:dotIdx]
if alias[0] == '#' {
continue
}
suffix := rest[dotIdx+1:]
idx, known := aliasSet[alias] idx, known := aliasSet[alias]
if !known { if !known {

View File

@@ -13,11 +13,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/common"
. "github.com/yusing/godoxy/internal/entrypoint" . "github.com/yusing/godoxy/internal/entrypoint"
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
"github.com/yusing/godoxy/internal/route" "github.com/yusing/godoxy/internal/route"
routeTypes "github.com/yusing/godoxy/internal/route/types" routeTypes "github.com/yusing/godoxy/internal/route/types"
"github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
) )
type noopResponseWriter struct { type noopResponseWriter struct {
@@ -50,15 +48,13 @@ func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
} }
func BenchmarkEntrypointReal(b *testing.B) { func BenchmarkEntrypointReal(b *testing.B) {
task := task.GetTestTask(b) ep := NewTestEntrypoint(b, nil)
ep := NewEntrypoint(task, nil)
req := http.Request{ req := http.Request{
Method: "GET", Method: http.MethodGet,
URL: &url.URL{Path: "/", RawPath: "/"}, URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld", Host: "test.domain.tld",
} }
ep.SetFindRouteDomains([]string{}) ep.SetFindRouteDomains([]string{})
entrypoint.SetCtx(task, ep)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1") w.Header().Set("Content-Length", "1")
@@ -85,7 +81,7 @@ func BenchmarkEntrypointReal(b *testing.B) {
Alias: "test", Alias: "test",
Scheme: routeTypes.SchemeHTTP, Scheme: routeTypes.SchemeHTTP,
Host: host, Host: host,
Port: route.Port{Proxy: portInt}, Port: route.Port{Listening: 1000, Proxy: portInt},
HealthCheck: types.HealthCheckConfig{Disable: true}, HealthCheck: types.HealthCheckConfig{Disable: true},
}) })
@@ -94,7 +90,7 @@ func BenchmarkEntrypointReal(b *testing.B) {
var w noopResponseWriter var w noopResponseWriter
server, ok := ep.GetServer(common.ProxyHTTPAddr) server, ok := ep.GetServer(":1000")
if !ok { if !ok {
b.Fatal("server not found") b.Fatal("server not found")
} }
@@ -112,15 +108,13 @@ func BenchmarkEntrypointReal(b *testing.B) {
} }
func BenchmarkEntrypoint(b *testing.B) { func BenchmarkEntrypoint(b *testing.B) {
task := task.GetTestTask(b) ep := NewTestEntrypoint(b, nil)
ep := NewEntrypoint(task, nil)
req := http.Request{ req := http.Request{
Method: "GET", Method: http.MethodGet,
URL: &url.URL{Path: "/", RawPath: "/"}, URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld", Host: "test.domain.tld",
} }
ep.SetFindRouteDomains([]string{}) ep.SetFindRouteDomains([]string{})
entrypoint.SetCtx(task, ep)
r, err := route.NewStartedTestRoute(b, &route.Route{ r, err := route.NewStartedTestRoute(b, &route.Route{
Alias: "test", Alias: "test",

View File

@@ -36,7 +36,7 @@ type Provider interface {
// Status and monitoring // Status and monitoring
ContainerStatus(ctx context.Context) (ContainerStatus, error) ContainerStatus(ctx context.Context) (ContainerStatus, error)
Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan gperr.Error) Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan error)
// Cleanup // Cleanup
Close() Close()

View File

@@ -303,7 +303,7 @@ curl "http://localhost:8080/api/uptime?period=1d&limit=20&offset=0&keyword=docke
```javascript ```javascript
const ws = new WebSocket( const ws = new WebSocket(
"ws://localhost:8080/api/uptime?period=1m&interval=5s" "ws://localhost:8080/api/uptime?period=1m&interval=5s",
); );
ws.onmessage = (event) => { ws.onmessage = (event) => {

View File

@@ -2,11 +2,12 @@ package serialization_test
import ( import (
"bytes" "bytes"
"errors"
"net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
gperr "github.com/yusing/goutils/errs"
) )
type TestStruct struct { type TestStruct struct {
@@ -14,18 +15,17 @@ type TestStruct struct {
Value2 int `json:"value2"` Value2 int `json:"value2"`
} }
func (t *TestStruct) Validate() gperr.Error { func (t *TestStruct) Validate() error {
if t.Value == "" { if t.Value == "" {
return gperr.New("value is required") return errors.New("value is required")
} }
if t.Value2 != 0 && (t.Value2 < 5 || t.Value2 > 10) { if t.Value2 != 0 && (t.Value2 < 5 || t.Value2 > 10) {
return gperr.New("value2 must be between 5 and 10") return errors.New("value2 must be between 5 and 10")
} }
return nil return nil
} }
func TestGinBinding(t *testing.T) { func TestGinBinding(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input string input string
@@ -40,7 +40,7 @@ func TestGinBinding(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var dst TestStruct var dst TestStruct
body := bytes.NewBufferString(tt.input) body := bytes.NewBufferString(tt.input)
req := httptest.NewRequest("POST", "/", body) req := httptest.NewRequest(http.MethodPost, "/", body)
err := serialization.GinJSONBinding{}.Bind(req, &dst) err := serialization.GinJSONBinding{}.Bind(req, &dst)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("%s: Bind() error = %v, wantErr %v", tt.name, err, tt.wantErr) t.Errorf("%s: Bind() error = %v, wantErr %v", tt.name, err, tt.wantErr)

View File

@@ -1,24 +1,23 @@
package serialization package serialization
import ( import (
"errors"
"reflect" "reflect"
"testing" "testing"
gperr "github.com/yusing/goutils/errs"
) )
// Test cases for when *T implements CustomValidator but T is passed in // Test cases for when *T implements CustomValidator but T is passed in
type CustomValidatingInt int type CustomValidatingInt int
func (c *CustomValidatingInt) Validate() gperr.Error { func (c *CustomValidatingInt) Validate() error {
if c == nil { if c == nil {
return gperr.New("pointer int cannot be nil") return errors.New("pointer int cannot be nil")
} }
if *c <= 0 { if *c <= 0 {
return gperr.New("int must be positive") return errors.New("int must be positive")
} }
if *c > 100 { if *c > 100 {
return gperr.New("int must be <= 100") return errors.New("int must be <= 100")
} }
return nil return nil
} }
@@ -26,12 +25,12 @@ func (c *CustomValidatingInt) Validate() gperr.Error {
// Test cases for when T implements CustomValidator but *T is passed in // Test cases for when T implements CustomValidator but *T is passed in
type CustomValidatingFloat float64 type CustomValidatingFloat float64
func (c CustomValidatingFloat) Validate() gperr.Error { func (c CustomValidatingFloat) Validate() error {
if c < 0 { if c < 0 {
return gperr.New("float must be non-negative") return errors.New("float must be non-negative")
} }
if c > 1000 { if c > 1000 {
return gperr.New("float must be <= 1000") return errors.New("float must be <= 1000")
} }
return nil return nil
} }

View File

@@ -1,23 +1,22 @@
package serialization package serialization
import ( import (
"errors"
"reflect" "reflect"
"testing" "testing"
gperr "github.com/yusing/goutils/errs"
) )
type CustomValidatingPointerString string type CustomValidatingPointerString string
func (c *CustomValidatingPointerString) Validate() gperr.Error { func (c *CustomValidatingPointerString) Validate() error {
if c == nil { if c == nil {
return gperr.New("pointer string cannot be nil") return errors.New("pointer string cannot be nil")
} }
if *c == "" { if *c == "" {
return gperr.New("string cannot be empty") return errors.New("string cannot be empty")
} }
if len(*c) < 2 { if len(*c) < 2 {
return gperr.New("string must be at least 2 characters") return errors.New("string must be at least 2 characters")
} }
return nil return nil
} }

View File

@@ -1,20 +1,19 @@
package serialization package serialization
import ( import (
"errors"
"reflect" "reflect"
"testing" "testing"
gperr "github.com/yusing/goutils/errs"
) )
type CustomValidatingString string type CustomValidatingString string
func (c CustomValidatingString) Validate() gperr.Error { func (c CustomValidatingString) Validate() error {
if c == "" { if c == "" {
return gperr.New("string cannot be empty") return errors.New("string cannot be empty")
} }
if len(c) < 2 { if len(c) < 2 {
return gperr.New("string must be at least 2 characters") return errors.New("string must be at least 2 characters")
} }
return nil return nil
} }

View File

@@ -1,25 +1,24 @@
package serialization package serialization
import ( import (
"errors"
"reflect" "reflect"
"testing" "testing"
gperr "github.com/yusing/goutils/errs"
) )
type CustomValidatingPointerStruct struct { type CustomValidatingPointerStruct struct {
Value string Value string
} }
func (c *CustomValidatingPointerStruct) Validate() gperr.Error { func (c *CustomValidatingPointerStruct) Validate() error {
if c == nil { if c == nil {
return gperr.New("pointer struct cannot be nil") return errors.New("pointer struct cannot be nil")
} }
if c.Value == "" { if c.Value == "" {
return gperr.New("value cannot be empty") return errors.New("value cannot be empty")
} }
if len(c.Value) < 3 { if len(c.Value) < 3 {
return gperr.New("value must be at least 3 characters") return errors.New("value must be at least 3 characters")
} }
return nil return nil
} }

View File

@@ -1,22 +1,21 @@
package serialization package serialization
import ( import (
"errors"
"reflect" "reflect"
"testing" "testing"
gperr "github.com/yusing/goutils/errs"
) )
type CustomValidatingStruct struct { type CustomValidatingStruct struct {
Value string Value string
} }
func (c CustomValidatingStruct) Validate() gperr.Error { func (c CustomValidatingStruct) Validate() error {
if c.Value == "" { if c.Value == "" {
return gperr.New("value cannot be empty") return errors.New("value cannot be empty")
} }
if len(c.Value) < 3 { if len(c.Value) < 3 {
return gperr.New("value must be at least 3 characters") return errors.New("value must be at least 3 characters")
} }
return nil return nil
} }

View File

@@ -40,7 +40,7 @@ Alias to `events.Event` for convenience.
type Watcher interface { type Watcher interface {
// Events returns channels for receiving events and errors. // Events returns channels for receiving events and errors.
// The channels are closed when the context is cancelled. // The channels are closed when the context is cancelled.
Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) Events(ctx context.Context) (<-chan Event, <-chan error)
} }
``` ```