diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7f2b820..f6c8d3f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,38 +17,35 @@ 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 - name: Setup Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: 'go.mod' + - + name: Test Setup + run: | + make install-gotestsum - name: Lint 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 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/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..34be0c0 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,23 @@ 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{}, + wrapped: nil, + } asertoErrors[code] = asertoError return asertoError @@ -50,55 +54,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 +94,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 +106,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 +154,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 +171,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 +203,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 +289,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..0a13746 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,37 @@ 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, + Reason: "", + }) + 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 +137,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 +150,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.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)