From b908917020b331ea35e5f74ebcc48b5b74b4cba9 Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:21:09 +0200 Subject: [PATCH 1/4] slim AsertoError: single wrapped error, fixed grpc/http correlation, drop dead methods Rebased on top of the concurrent inner-error/formatting fixes (Fields() recursion, Msg-before-cause ordering, errors.As-based traversal) already on main; this does not touch those, it builds on them. Copy() shallow-copied the errs slice, letting siblings derived from the same parent share a backing array and silently corrupt each other's wrapped error once append had slack (azm already hits this pattern via nested Err() calls). Replacing errs []error with a single wrapped error removes the aliasing by construction. Fields()'s recursion into inner AsertoErrors is preserved, adapted to the single wrapped field. GRPCStatus() never wrote HTTPCode into the ErrorInfo metadata that CustomErrorHandler reads back, so the grpc<->HTTP status correlation this package exists for only worked because topaz's gerr_middleware hand-rolled it a second time. Added ErrorInfo()/fixed GRPCStatus() to be the single source of truth. FromGRPCStatus also no longer mutates the registered singleton in place. Removed Int/Int32/Int64/Bool/Duration/Time/FromReader/Interface/SameAs/ Equals/WithGRPCStatus/WithHTTPStatus: zero real call sites across go-directory, topaz, and azm. WithGRPCStatus's one usage in topaz is better served by a dedicated sentinel error than an override. Renamed StatusCode -> GRPCCode for clarity. Downstream repos need updates for the rename and removed methods; DESIGN.md has the full rationale and sequencing. Co-Authored-By: Claude Sonnet 5 --- DESIGN.md | 251 +++++++++++++++++++++++++++++++++++++++++++++++++ errors.go | 240 +++++++++++++--------------------------------- errors_test.go | 98 ++++++------------- 3 files changed, 348 insertions(+), 241 deletions(-) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..26306ee --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,251 @@ +# AsertoError & Logging Redesign + +Status: **Draft — for discussion, no code changes yet.** + +## 1. Motivation + +`AsertoError` was built to do three jobs at once: a sentinel-error registry, a +structured-context builder (mirroring zerolog's `Event` API), and a +grpc\<->HTTP status bridge. Reviewing the type against how it's actually used +in the three consumers that matter (`go-directory/pkg/derr`, `topaz`, `azm`) +turned up two confirmed bugs and a design mismatch between what the package +offers and what's actually used: + +- `Copy()` shallow-copies the `errs []error` slice (`errors.go:62-77`), so two + errors derived from the same parent via `.Err()` can share a backing array + and silently overwrite each other once Go's slice growth leaves capacity + slack. Reproduced directly — a 3-deep chain branched into two siblings and + one silently absorbed the other's appended error. `azm` already does + `derr.ErrX.Err(derr.ErrY.Msgf(...))` (`safe/relation.go` and others), so + this is live exposure, not a lab curiosity. +- `FromGRPCStatus` (`errors.go:280-301`) mutates the package-level singleton + returned by `asertoErrors[code]` in place (`result.data = t.Metadata`) + instead of operating on a copy. Reproduced directly — calling it once + permanently changes the shared `data` on every future use of that error + code, and it's an unsynchronized concurrent write on top of that. +- The package's own `GRPCStatus()` (`errors.go:249-260`) is never called by + any of the three consumers, and it's the broken half of the design: it + never writes `HTTPCode` into the `errdetails.ErrorInfo` metadata that + `CustomErrorHandler` (`custom_error_handler.go`) reads back out. The actual + grpc\<->HTTP correlation in production is hand-rolled a second time in + `topaz/internal/grpc/middlewares/gerr/gerr_middleware.go`, which manually + calls `.Int(aerr.HTTPStatusErrorMetadata, asertoErr.HTTPCode)` before + building the status — the one thing this package exists to provide is + duplicated and has drifted from the library's own version of it. +- Usage sweep across `go-directory`, `topaz`, `azm` (grep for real + `AsertoError`-typed call chains, not zerolog's identically-named builder + methods): `NewAsertoError`, `.Msg`/`.Msgf`, and `.Err` are the workhorses. + `.Str`/`.Int32`/`.Int64`/`.Bool`/`.Duration`/`.Time`/`.FromReader`/ + `.Interface`/`SameAs`/`Equals`/`WithGRPCStatus`/`WithHTTPStatus` have zero + real call sites in any of the three repos. `aerr.Logger`/`.Data()`/ + `.Fields()`/`ContextError` have exactly one call site — `gerr_middleware.go` + — but it's the single choke point every gRPC error response in `topaz` + passes through, so it's load-bearing despite the low count. `azm` never + touches the logging surface at all, yet imports it transitively (zerolog, + `grpc-ecosystem/grpc-gateway/v2/runtime`) for zero benefit. + +This is why the fix isn't a patch to `Copy()` — the logging/gRPC-gateway +coupling and the wrapping mechanism are the same code paths that need to +change shape anyway. + +## 2. Non-goals + +- Not rewriting `topaz`'s or `azm`'s own general-purpose logging (their + existing `.Str().Msg()` zerolog call sites outside the errors-package + boundary are untouched). +- Not touching the error *registries* (`derr`, `internal/eds/pkg/ds/error.go`) + — they keep calling `NewAsertoError(code, grpcCode, httpCode, msg)` + unchanged. +- Not picking a new wire format for cross-service error propagation — + `errdetails.ErrorInfo` inside the grpc `Status` stays; it's the right + mechanism for the stated goal (survives the grpc-gateway boundary, + standard idiom). + +## 3. Proposed design + +### P1 — Slim the core type + +Keep exactly what's used; deprecate the rest for one release cycle before +removal (see §5). + +```go +type AsertoError struct { + Code string + GRPCCode codes.Code + HTTPCode int + Message string + data map[string]string // copy-on-write, as today + wrapped error // see P3 — was []error +} +``` + +Kept: `NewAsertoError`, `Msg`/`Msgf`, `Err`, `Str` (used to attach a single +metadata field — keep, low cost, unlike the rest of the typed setters), +`Data`/`Fields`, `Error`/`Unwrap`, `Copy`, `Ctx`. + +Deprecated for removal: `Int`/`Int32`/`Int64`/`Bool`/`Duration`/`Time`/ +`FromReader`/`Interface`, `SameAs`, `Equals`, `WithGRPCStatus`, +`WithHTTPStatus`. Zero real call sites found across all three consumers. + +### P2 — One source of truth for grpc\<->HTTP correlation + +Replace the broken `GRPCStatus()` with a helper that both the library and +`gerr_middleware.go` can use, including the `Reason` field the middleware +currently sets itself (a correlation/error ID, which is caller-specific and +shouldn't be generated by this package): + +```go +// ErrorInfo returns the errdetails.ErrorInfo for this error, with HTTPCode +// folded into Metadata under HTTPStatusErrorMetadata. +func (e *AsertoError) ErrorInfo(reason string) *errdetails.ErrorInfo + +// GRPCStatus builds the grpc Status via ErrorInfo(""). Satisfies the +// GRPCStatus() *status.Status interface some grpc tooling expects. +func (e *AsertoError) GRPCStatus() *status.Status +``` + +`gerr_middleware.go` then becomes: + +```go +errResult, err := status.New(asertoErr.GRPCCode, asertoErr.Error()). + WithDetails(asertoErr.ErrorInfo(errID.String())) +``` + +removing its hand-rolled duplicate of `WithDetails`/`ErrorInfo` construction, +and guaranteeing `CustomErrorHandler` always finds the HTTP status key, +because the same code path is now the only path. + +### P3 — One wrapping mechanism + +`errs []error` plus custom `Error()`/`Unwrap()` string-joining coexists today +with `pkg/errors`-style wrapping (`ContextError.Cause`) and native +`errors.Unwrap`. Nothing in `go-directory`, `topaz`, or `azm` chains more than +one `.Err()` call in practice (the multi-error chain is only exercised by +this package's own tests). Replace the slice with a single `wrapped error` +field: + +```go +func (e *AsertoError) Err(err error) *AsertoError { + if err == nil { + return e + } + c := e.Copy() + c.wrapped = err + return c +} +``` + +This removes the aliasing bug by construction — there's no shared backing +array left to alias — rather than papering over it with a deep copy that +still leaves a subtler version of the same hazard if the slice comes back +later. If genuine multi-error wrapping turns out to be needed somewhere, the +stdlib `errors.Join` is the right tool for that, orthogonal to this type. + +*This changes `Error()`'s multi-wrap output format (`TestDoubleCerr`, +`TestError`'s 4-chain case) — those tests get rewritten, not preserved.* + +### P4 — Logging: `slog` at the boundary, zerolog stays the engine in `topaz` + +- `errors` core module drops the `zerolog` dependency entirely. +- `AsertoError` implements `slog.LogValuer`: + + ```go + func (e *AsertoError) LogValue() slog.Value + ``` + + replacing `MarshalZerologObject`. +- A new `ctxlog` package owns the "logger in context" convention that + `ContextError`/`extractLogger` already half-implement today, but as a + standalone utility rather than only reachable by wrapping an error + (`log/slog` deliberately ships no such helper — this has to be owned): + + ```go + package ctxlog + + func With(ctx context.Context, logger *slog.Logger) context.Context + func From(ctx context.Context) *slog.Logger + ``` + +- `Logger(err error)` walks the error chain like today but returns + `*slog.Logger`, sourced via `ctxlog.From`. +- `topaz` keeps zerolog as its actual encoder by writing (or adopting) a + small `slog.Handler` backed by a `zerolog.Logger` — the 4-method interface + (`Enabled`/`Handle`/`WithAttrs`/`WithGroup`) is cheap to own directly rather + than take on a third-party bridge dependency. Two things this bridge must + handle deliberately, or they silently regress: + - `gerr_middleware.go`'s `.Stack()` call (via `pkg/errors`) — no native + `slog` stack-trace attr convention exists; the handler needs to recognize + and format it. + - Allocation behavior — zerolog's whole pitch is zero-allocation logging; + routing everything through generic `slog.Record`/`Attr` conversion adds + some. Almost certainly noise next to network/DB calls on topaz's request + path, but worth one benchmark on the per-decision logging path before + assuming it's free, not after. + +### P5 — Dependency isolation needs module boundaries, not just packages + +`azm` importing `errors` today pulls in zerolog and +`grpc-ecosystem/grpc-gateway/v2/runtime` transitively even though it uses +neither. Moving `CustomErrorHandler` and the zerolog bridge into +*subpackages* of the same module doesn't fully fix this — Go's module graph +still resolves those requirements for anything that imports the module at +all. Getting `azm` genuinely free of zerolog/grpc-gateway requires **separate +`go.mod` files**: + +- `github.com/aserto-dev/errors` — core type, no zerolog, no grpc-gateway. +- `github.com/aserto-dev/errors/zlog` (or similar) — the `slog.Handler` + zerolog bridge + `ctxlog`. Only `topaz` imports it. +- `github.com/aserto-dev/errors/httpgw` — `CustomErrorHandler`. Only whatever + actually runs a grpc-gateway `ServeMux` imports it. + +This is a real increase in release/versioning overhead (three modules to tag +instead of one) — flagged as an open question in §6, not a foregone +conclusion. + +## 4. What doesn't change + +- `NewAsertoError(code, grpcCode, httpCode, msg)` registry pattern — used + ~45+ times in `go-directory/derr` alone, untouched. +- `errdetails.ErrorInfo` inside grpc `Status` as the wire mechanism. +- Context-carried logger as the standard, replacing the current + inconsistent mix of direct-pass and context-pass in `topaz` (per prior + discussion) — this is what `ctxlog` formalizes. + +## 5. Sequencing + +| Phase | Scope | Depends on | +|---|---|---| +| 0 | Decide open questions below | — | +| 1 | `errors`: P1 (slim type) + P3 (single `wrapped`) + P2 (`ErrorInfo`/`GRPCStatus` fix) — pure logic, no logging changes | Phase 0 | +| 2 | `errors`: split `zlog`/`httpgw` into separate modules (P5), drop zerolog/grpc-gateway from core `go.mod` | Phase 0 (module-split decision) | +| 3 | `errors/zlog`: `ctxlog` + zerolog-backed `slog.Handler` + `LogValue()` on `AsertoError` (P4) | Phase 2 | +| 4 | `topaz`: migrate `gerr_middleware.go` to `ErrorInfo()`/`ctxlog`, removing its duplicate grpc-status construction | Phase 1, 3 | +| 5 | `topaz`/`azm`: bump to the new `errors` version; deprecated methods removed once the same repo-search technique used in this review confirms zero remaining call sites | Phase 4 | + +Both `go-directory/derr` (`v0.34.0` pinned in `topaz`) and `azm` +(`v0.0.17` pinned — notably far behind `topaz`'s pin) are still pre-1.0, so +Go's semver convention allows breaking changes on a minor bump without a +`/v2` module path. That gives more room to move than a post-1.0 package +would have, but `azm`'s stale pin is worth checking — it may mean nobody +has attempted an upgrade in a while for a reason not visible from usage +grep alone. + +## 6. Open questions + +1. **Module split (P5).** Worth the added release overhead of three + versioned modules instead of one, to fully isolate `azm` from + zerolog/grpc-gateway? Or is "core module free of those deps, `topaz`-only + subpackages that `azm` simply never imports" (accepting the module-graph + resolution cost, not the compile cost) good enough in practice? +2. **Urgency of the `Copy()` fix.** P3 removes the aliasing bug by + construction, but it's bundled with the larger redesign. Given `azm` + already exercises the vulnerable pattern in production, does the + `errs`→`wrapped` change (or a minimal interim deep-copy patch) need to + ship on its own first, ahead of the rest of this doc? +3. **Deprecation window.** How long do the dead builder methods stay present + (marked `Deprecated:`) before hard removal — tied to how quickly `topaz`/ + `azm` can bump their pins. +4. **Where `ctxlog` lives.** It's a generically useful "logger in context" + helper with no inherent connection to errors. Keep it inside this module + (even if split per P5) since it grew out of `ContextError`, or should it + be its own small standalone module from the start? diff --git a/errors.go b/errors.go index 587a94e..7fe0c1e 100644 --- a/errors.go +++ b/errors.go @@ -3,12 +3,9 @@ package errors import ( "context" "fmt" - "io" "maps" "net/http" "strconv" - "strings" - "time" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -31,16 +28,22 @@ var ( // AsertoError represents a well known error // coming from an Aserto service. type AsertoError struct { - Code string - StatusCode codes.Code - Message string - HTTPCode int - data map[string]string - errs []error -} - -func NewAsertoError(code string, statusCode codes.Code, httpCode int, msg string) *AsertoError { - asertoError := &AsertoError{code, statusCode, msg, httpCode, map[string]string{}, nil} + Code string + GRPCCode codes.Code + HTTPCode int + Message string + data map[string]string + wrapped error +} + +func NewAsertoError(code string, grpcCode codes.Code, httpCode int, msg string) *AsertoError { + asertoError := &AsertoError{ + Code: code, + GRPCCode: grpcCode, + HTTPCode: httpCode, + Message: msg, + data: map[string]string{}, + } asertoErrors[code] = asertoError return asertoError @@ -50,55 +53,34 @@ func (e *AsertoError) Data() map[string]string { return e.Copy().data } -// SameAs returns true if the provided error is an AsertoError -// and has the same error code. -func (e *AsertoError) SameAs(err error) bool { - var aErr *AsertoError - if ok := errors.As(err, &aErr); err == nil || !ok { - return false - } - - return aErr.Code == e.Code -} - func (e *AsertoError) Copy() *AsertoError { dataCopy := make(map[string]string, len(e.data)) maps.Copy(dataCopy, e.data) return &AsertoError{ - Code: e.Code, - StatusCode: e.StatusCode, - Message: e.Message, - data: dataCopy, - errs: e.errs, - HTTPCode: e.HTTPCode, + Code: e.Code, + GRPCCode: e.GRPCCode, + HTTPCode: e.HTTPCode, + Message: e.Message, + data: dataCopy, + wrapped: e.wrapped, } } func (e *AsertoError) Error() string { - errsMessage := "" - - if len(e.errs) > 0 { - errsMessage = e.errs[0].Error() + innerMessage := "" - for _, err := range e.errs[1:] { - errsMessage = errsMessage + colon + err.Error() - } + if e.wrapped != nil { + innerMessage = e.wrapped.Error() } - innerMessage := errsMessage - - if len(e.data) > 0 { - for k, v := range e.data { - if k == "msg" { - if innerMessage != "" { - innerMessage = colon + innerMessage - } - - innerMessage = v + innerMessage - } + if msg, ok := e.data[MessageKey]; ok { + if innerMessage != "" { + innerMessage = colon + innerMessage } + + innerMessage = msg + innerMessage } if innerMessage == "" { @@ -111,11 +93,9 @@ func (e *AsertoError) Error() string { func (e *AsertoError) Fields() map[string]any { result := make(map[string]any, len(e.data)) - for _, err := range e.errs { - var aerr *AsertoError - if ok := errors.As(err, &aerr); ok { - maps.Copy(result, aerr.Fields()) - } + var aerr *AsertoError + if errors.As(e.wrapped, &aerr) { + maps.Copy(result, aerr.Fields()) } for k, v := range e.data { @@ -125,14 +105,15 @@ func (e *AsertoError) Fields() map[string]any { return result } -// Err associates err with the AsertoError. +// Err associates err with the AsertoError, replacing any error previously +// associated via Err. func (e *AsertoError) Err(err error) *AsertoError { if err == nil { return e } c := e.Copy() - c.errs = append(c.errs, err) + c.wrapped = err return c } @@ -172,86 +153,16 @@ func (e *AsertoError) Str(key, value string) *AsertoError { return c } -func (e *AsertoError) Int(key string, value int) *AsertoError { - c := e.Copy() - c.data[key] = strconv.Itoa(value) - - return c -} - -func (e *AsertoError) Int32(key string, value int32) *AsertoError { - c := e.Copy() - c.data[key] = strconv.FormatInt(int64(value), 10) - - return c -} - -func (e *AsertoError) Int64(key string, value int64) *AsertoError { - c := e.Copy() - c.data[key] = strconv.FormatInt(value, 10) - - return c -} - -func (e *AsertoError) Bool(key string, value bool) *AsertoError { - c := e.Copy() - c.data[key] = strconv.FormatBool(value) - - return c -} - -func (e *AsertoError) Duration(key string, value time.Duration) *AsertoError { - c := e.Copy() - c.data[key] = value.String() - - return c -} - -func (e *AsertoError) Time(key string, value time.Time) *AsertoError { - c := e.Copy() - c.data[key] = value.UTC().Format(time.RFC3339) - - return c -} - -func (e *AsertoError) FromReader(key string, value io.Reader) *AsertoError { - buf := &strings.Builder{} - - if _, err := io.Copy(buf, value); err != nil { - return e.Err(err) - } - - c := e.Copy() - c.data[key] = buf.String() - - return c -} - -func (e *AsertoError) Interface(key string, value any) *AsertoError { - c := e.Copy() - c.data[key] = fmt.Sprintf("%+v", value) - - return c -} - func (e *AsertoError) Unwrap() error { if e == nil { return nil } - if len(e.errs) > 0 { - return e.errs[len(e.errs)-1] - } - - return nil + return e.wrapped } func (e *AsertoError) Cause() error { - if len(e.errs) > 0 { - return e.errs[len(e.errs)-1] - } - - return nil + return e.wrapped } func (e *AsertoError) MarshalZerologObject(event *zerolog.Event) { @@ -259,32 +170,29 @@ func (e *AsertoError) MarshalZerologObject(event *zerolog.Event) { event.Fields(e.Fields()) } -func (e *AsertoError) GRPCStatus() *status.Status { - errResult := status.New(e.StatusCode, e.Message) +// ErrorInfo returns the errdetails.ErrorInfo for this error, with HTTPCode +// folded into Metadata under HTTPStatusErrorMetadata so it survives the +// grpc-gateway boundary (see CustomErrorHandler). +func (e *AsertoError) ErrorInfo(reason string) *errdetails.ErrorInfo { + data := e.Data() + data[HTTPStatusErrorMetadata] = strconv.Itoa(e.HTTPCode) - errResult, err := errResult.WithDetails(&errdetails.ErrorInfo{ - Metadata: e.Data(), + return &errdetails.ErrorInfo{ + Reason: reason, + Metadata: data, Domain: e.Code, - }) - if err != nil { - return status.New(codes.Internal, "internal failure setting up error details, please contact the administrator") } - - return errResult } -func (e *AsertoError) WithGRPCStatus(grpcCode codes.Code) *AsertoError { - c := e.Copy() - c.StatusCode = grpcCode - - return c -} +func (e *AsertoError) GRPCStatus() *status.Status { + errResult := status.New(e.GRPCCode, e.Message) -func (e *AsertoError) WithHTTPStatus(httpStatus int) *AsertoError { - c := e.Copy() - c.HTTPCode = httpStatus + errResult, err := errResult.WithDetails(e.ErrorInfo("")) + if err != nil { + return status.New(codes.Internal, "internal failure setting up error details, please contact the administrator") + } - return c + return errResult } func (e *AsertoError) Ctx(ctx context.Context) error { @@ -294,28 +202,28 @@ func (e *AsertoError) Ctx(ctx context.Context) error { // FromGRPCStatus returns an Aserto error based on a given grpcStatus. The details that are not of type errdetails.ErrorInfo are dropped. // and if there are details from multiple errors, the aserto error will be constructed based on the first one. func FromGRPCStatus(grpcStatus status.Status) *AsertoError { - var result *AsertoError - if len(grpcStatus.Details()) == 0 { return ErrUnknown.Msg(grpcStatus.Message()) } for _, detail := range grpcStatus.Details() { - if t, ok := detail.(*errdetails.ErrorInfo); ok { - result = asertoErrors[t.GetDomain()] - if result == nil { - return nil - } - - result.data = t.GetMetadata() + t, ok := detail.(*errdetails.ErrorInfo) + if !ok { + continue } - if result != nil { - break + registered := asertoErrors[t.GetDomain()] + if registered == nil { + return nil } + + result := registered.Copy() + result.data = t.GetMetadata() + + return result } - return result + return nil } // Logger retrieves the most inner logger associated with an error. @@ -380,22 +288,6 @@ func UnwrapAsertoError(err error) *AsertoError { return nil } -// Equals returns true if the given errors are Aserto errors with the same code or both of them are nil. -func Equals(err1, err2 error) bool { - asertoErr1 := UnwrapAsertoError(err1) - asertoErr2 := UnwrapAsertoError(err2) - - if err1 == nil && err2 == nil { - return true - } - - if asertoErr1 == nil || asertoErr2 == nil { - return false - } - - return asertoErr1.Code == asertoErr2.Code -} - func CodeToAsertoError(code string) *AsertoError { return asertoErrors[code] } diff --git a/errors_test.go b/errors_test.go index 9ee1b7d..6e6aa95 100644 --- a/errors_test.go +++ b/errors_test.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "os" + "strconv" "testing" "github.com/pkg/errors" @@ -21,8 +22,8 @@ var ( ErrAlreadyExists = newErr("E10002", codes.AlreadyExists, http.StatusConflict, "already exists") ) -func newErr(code string, statusCode codes.Code, httpCode int, msg string) *cerr.AsertoError { - return cerr.NewAsertoError(code, statusCode, httpCode, msg) +func newErr(code string, grpcCode codes.Code, httpCode int, msg string) *cerr.AsertoError { + return cerr.NewAsertoError(code, grpcCode, httpCode, msg) } func TestDoubleCerr(t *testing.T) { @@ -63,6 +64,7 @@ func TestError(t *testing.T) { err := ErrNotFound.Msg("bla").Err(errors.New("boom")) err2 := ErrNotFound.Msg("bla").Msg("ala") err3 := ErrNotFound.Err(errors.New("boom")).Msg("bla").Msg("ala") + // Err replaces rather than accumulates, so chaining Err twice keeps only the last one. err4 := ErrNotFound.Err(errors.New("boom")).Err(errors.New("pow")).Msg("bla").Msg("ala") err5 := ErrNotFound.Err(errors.New("boom")) err6 := ErrNotFound.Err(errors.New("boom")).Err(errors.New("pow")) @@ -71,31 +73,19 @@ func TestError(t *testing.T) { assert.ErrorContains(err, "E10001 not found: bla: boom") assert.ErrorContains(err2, "E10001 not found: bla: ala") assert.ErrorContains(err3, "E10001 not found: bla: ala: boom") - assert.ErrorContains(err4, "E10001 not found: bla: ala: boom: pow") + assert.ErrorContains(err4, "E10001 not found: bla: ala: pow") assert.ErrorContains(err5, "E10001 not found: boom") - assert.ErrorContains(err6, "E10001 not found: boom: pow") + assert.ErrorContains(err6, "E10001 not found: pow") assert.ErrorContains(err7, "E10001 not found: bla") } -func TestWithGrpcStatusCode(t *testing.T) { - assert := require.New(t) - err := ErrNotFound.WithGRPCStatus(codes.Canceled) - assert.Equal(codes.Canceled, err.StatusCode) -} - -func TestWithHttpStatusCode(t *testing.T) { - assert := require.New(t) - err := ErrNotFound.WithHTTPStatus(http.StatusAccepted) - assert.Equal(http.StatusAccepted, err.HTTPCode) -} - func TestFromGRPCStatus(t *testing.T) { assert := require.New(t) initialErr := ErrNotFound initialErr = initialErr.Str("email", "testuser@mail.com").Msg("foo") - grpcStatus := status.New(initialErr.StatusCode, initialErr.Error()) + grpcStatus := status.New(initialErr.GRPCCode, initialErr.Error()) grpcStatus, err := grpcStatus.WithDetails(&errdetails.ErrorInfo{ Reason: "1234", @@ -108,63 +98,36 @@ func TestFromGRPCStatus(t *testing.T) { transformedErr := cerr.FromGRPCStatus(*grpcStatus) - assert.True(initialErr.SameAs(transformedErr)) - + assert.Equal(initialErr.Code, transformedErr.Code) assert.Equal(initialErr.Error(), transformedErr.Error()) assert.Equal(initialErr.Message, transformedErr.Message) } -func TestUnwrapNilErr(t *testing.T) { +// FromGRPCStatus must not mutate the registered singleton it looks up. +func TestFromGRPCStatusDoesNotMutateSingleton(t *testing.T) { assert := require.New(t) - err := cerr.UnwrapAsertoError(nil) - - assert.Nil(err) -} - -func TestEquals(t *testing.T) { - assert := require.New(t) - - err1 := ErrAlreadyExists.Msgf("error 1").Str("key1", "val1").Err(errors.New("boom")) - err2 := ErrAlreadyExists.Msgf("error 2").Str("key2", "val2").Err(errors.New("zoom")) - - assert.True(cerr.Equals(err1, err2)) -} - -func TestEqualsNil(t *testing.T) { - assert := require.New(t) + before := ErrNotFound.Data() - assert.True(cerr.Equals(nil, nil)) -} - -func TestEqualsOneNil(t *testing.T) { - assert := require.New(t) - - assert.False(cerr.Equals(ErrNotFound, nil)) -} - -func TestEqualsNormalErrorOneNil(t *testing.T) { - assert := require.New(t) - - assert.False(cerr.Equals(errors.New("boom"), nil)) -} + e := ErrNotFound.Str("email", "testuser@mail.com") + grpcStatus := status.New(e.GRPCCode, e.Error()) + grpcStatus, err := grpcStatus.WithDetails(&errdetails.ErrorInfo{ + Metadata: e.Data(), + Domain: e.Code, + }) + assert.NoError(err) -func TestEqualsErrCerr(t *testing.T) { - assert := require.New(t) + _ = cerr.FromGRPCStatus(*grpcStatus) - assert.False(cerr.Equals(errors.New("boom"), ErrNotFound)) + assert.Equal(before, ErrNotFound.Data()) } -func TestEqualsFalse(t *testing.T) { +func TestUnwrapNilErr(t *testing.T) { assert := require.New(t) - assert.False(cerr.Equals(ErrAlreadyExists, ErrNotFound)) -} - -func TestEqualsNormalErrors(t *testing.T) { - assert := require.New(t) + err := cerr.UnwrapAsertoError(nil) - assert.False(cerr.Equals(errors.New("boom1"), errors.New("boom2"))) + assert.Nil(err) } func TestCodeToAsertoError(t *testing.T) { @@ -173,7 +136,7 @@ func TestCodeToAsertoError(t *testing.T) { asertoErr := cerr.CodeToAsertoError("E10001") assert.NotNil(asertoErr) - assert.True(cerr.Equals(asertoErr, ErrNotFound)) + assert.Equal(ErrNotFound.Code, asertoErr.Code) } func TestCodeToAsertoErrorInvalidCode(t *testing.T) { @@ -186,20 +149,21 @@ func TestCodeToAsertoErrorInvalidCode(t *testing.T) { func TestWithGrpcError(t *testing.T) { assert := require.New(t) - aerr := cerr.NewAsertoError("E000001", codes.Unavailable, http.StatusServiceUnavailable, "failed to setup").WithGRPCStatus(codes.Aborted) + aerr := cerr.NewAsertoError("E000001", codes.Aborted, http.StatusServiceUnavailable, "failed to setup") berr := errors.Wrap(aerr, "new err") unAerr := cerr.UnwrapAsertoError(berr) assert.Equal(codes.Aborted, unAerr.GRPCStatus().Code()) } -func TestWithHttpError(t *testing.T) { +func TestErrorInfoIncludesHTTPStatus(t *testing.T) { assert := require.New(t) - aerr := cerr.NewAsertoError("E000001", codes.Unavailable, http.StatusServiceUnavailable, "failed to setup"). - WithHTTPStatus(http.StatusNotAcceptable) + aerr := cerr.NewAsertoError("E000003", codes.Unavailable, http.StatusNotAcceptable, "failed to setup") - unAerr := cerr.UnwrapAsertoError(aerr) - assert.Equal(http.StatusNotAcceptable, unAerr.HTTPCode) + info := aerr.ErrorInfo("req-1") + assert.Equal("req-1", info.Reason) + assert.Equal("E000003", info.Domain) + assert.Equal(strconv.Itoa(http.StatusNotAcceptable), info.Metadata[cerr.HTTPStatusErrorMetadata]) } // returns nil logger if error is nil. From 82b3e7e1dc47fff56333202b29e6362b83a824ba Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:54:02 +0200 Subject: [PATCH 2/4] update deps and CI Signed-off-by: Gert Drapers <1533850+gertd@users.noreply.github.com> --- .github/workflows/ci.yaml | 9 +++-- .golangci.yaml | 1 + errors.go | 1 + errors_test.go | 7 ++-- go.mod | 30 +++++++------- go.sum | 82 ++++++++++++++++----------------------- makefile | 6 +-- 7 files changed, 62 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7f2b820..46ec836 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,15 +17,14 @@ on: env: PRE_RELEASE: ${{ github.ref == 'refs/heads/main' && 'development' || '' }} GO_VER: "1.26" - GO_LANGCI_LINT_VER: v2.11.4 - GO_TESTSUM_VER: "1.13.0" + GO_LANGCI_LINT_VER: "v2.13.1" jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - @@ -33,6 +32,10 @@ jobs: uses: actions/setup-go@v6 with: go-version-file: 'go.mod' + - + name: Test Setup + run: | + make install-gotestsum - name: Lint uses: golangci/golangci-lint-action@v9 diff --git a/.golangci.yaml b/.golangci.yaml index ab223de..6ac0fd4 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -13,6 +13,7 @@ linters: - depguard - exhaustruct - gochecknoglobals # no configuration options + - gomodguard # deprecated (since v2.12.0) due to: new major version - nlreturn # redundant with wsl - noinlineerr - paralleltest diff --git a/errors.go b/errors.go index 7fe0c1e..34be0c0 100644 --- a/errors.go +++ b/errors.go @@ -43,6 +43,7 @@ func NewAsertoError(code string, grpcCode codes.Code, httpCode int, msg string) HTTPCode: httpCode, Message: msg, data: map[string]string{}, + wrapped: nil, } asertoErrors[code] = asertoError diff --git a/errors_test.go b/errors_test.go index 6e6aa95..0a13746 100644 --- a/errors_test.go +++ b/errors_test.go @@ -114,6 +114,7 @@ func TestFromGRPCStatusDoesNotMutateSingleton(t *testing.T) { grpcStatus, err := grpcStatus.WithDetails(&errdetails.ErrorInfo{ Metadata: e.Data(), Domain: e.Code, + Reason: "", }) assert.NoError(err) @@ -161,9 +162,9 @@ func TestErrorInfoIncludesHTTPStatus(t *testing.T) { aerr := cerr.NewAsertoError("E000003", codes.Unavailable, http.StatusNotAcceptable, "failed to setup") info := aerr.ErrorInfo("req-1") - assert.Equal("req-1", info.Reason) - assert.Equal("E000003", info.Domain) - assert.Equal(strconv.Itoa(http.StatusNotAcceptable), info.Metadata[cerr.HTTPStatusErrorMetadata]) + assert.Equal("req-1", info.GetReason()) + assert.Equal("E000003", info.GetDomain()) + assert.Equal(strconv.Itoa(http.StatusNotAcceptable), info.GetMetadata()[cerr.HTTPStatusErrorMetadata]) } // returns nil logger if error is nil. diff --git a/go.mod b/go.mod index c857ac8..7f9e060 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,25 @@ module github.com/aserto-dev/errors -go 1.25.0 +go 1.26 -toolchain go1.26.2 +toolchain go1.26.7 require ( - github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.35.0 - github.com/stretchr/testify v1.11.1 - google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d - google.golang.org/grpc v1.80.0 + github.com/rs/zerolog v1.35.1 + github.com/stretchr/testify v1.12.1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 + google.golang.org/grpc v1.83.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect - golang.org/x/net v0.52.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.35.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect - google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect ) diff --git a/go.sum b/go.sum index bb0bc14..a55f92c 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,7 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -13,57 +10,46 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= -github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI= -google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/makefile b/makefile index 2c11485..1ba47b0 100644 --- a/makefile +++ b/makefile @@ -16,10 +16,10 @@ EXT_BIN_DIR := ${EXT_DIR}/bin EXT_TMP_DIR := ${EXT_DIR}/tmp GO_VER := 1.26 -SVU_VER := 3.3.0 +SVU_VER := 3.4.1 GOTESTSUM_VER := 1.13.0 -GOLANGCI-LINT_VER := 2.11.4 -GORELEASER_VER := 2.14.1 +GOLANGCI-LINT_VER := 2.13.1 +GORELEASER_VER := 2.17.1 RELEASE_TAG := $$(${EXT_BIN_DIR}/svu current) From 8c5d2f8b728f184804d01dc6d23c282bcb0e97ae Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:59:08 +0200 Subject: [PATCH 3/4] update test & lint CI Signed-off-by: Gert Drapers <1533850+gertd@users.noreply.github.com> --- .github/workflows/ci.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 46ec836..bce5569 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,17 +41,11 @@ jobs: uses: golangci/golangci-lint-action@v9 with: version: ${{ env.GO_LANGCI_LINT_VER }} - verify: false # `golangci-lint config verify` checks against the latest schema instead of its own version. args: --timeout=30m - - - name: Test Setup - uses: gertd/action-gotestsum@v3.0.0 - with: - gotestsum_version: ${{ env.GO_TESTSUM_VER }} - name: Test run: | - gotestsum --format short-verbose -- -count=1 -parallel=1 -v -timeout=240s -coverprofile=cover.out -coverpkg=./... ./... + .ext/bin/gotestsum --format short-verbose -- -count=1 -parallel=1 -v -timeout=240s -coverprofile=cover.out -coverpkg=./... ./... - name: Upload code coverage uses: shogo82148/actions-goveralls@v1 From 233f2bd09e950ac6e8e8c428163f1ad19a3c36a4 Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:13:43 +0200 Subject: [PATCH 4/4] actions/setup-go@v7 Signed-off-by: Gert Drapers <1533850+gertd@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bce5569..f6c8d3f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 - name: Setup Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: 'go.mod' -