From be49391195be45c8129a5a50ef97e4efe4ace4ab Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 5 Sep 2026 04:18:25 -0400 Subject: [PATCH] feat(errconnect): add connect-go error code mapping Services adopting Connect RPC need to translate trogonerror codes into connect.Code without every consumer of the root module paying for the connect-go dependency. Signed-off-by: Yordis Prieto --- errconnect/code.go | 52 +++++++++++++++++++++++++++++++++++++++++ errconnect/code_test.go | 47 +++++++++++++++++++++++++++++++++++++ go.mod | 6 ++++- go.sum | 6 +++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 errconnect/code.go create mode 100644 errconnect/code_test.go diff --git a/errconnect/code.go b/errconnect/code.go new file mode 100644 index 0000000..4257bec --- /dev/null +++ b/errconnect/code.go @@ -0,0 +1,52 @@ +// Package errconnect adapts trogonerror to connectrpc.com/connect, translating +// error codes so trogonerror-based services can serve Connect RPC handlers. +package errconnect + +import ( + "connectrpc.com/connect" + + "github.com/TrogonStack/trogonerror" +) + +// Code maps the two vocabularies. Both are the canonical gRPC set and the +// numbers line up today, but a conversion written as a cast is a silent +// mistranslation the day either side adds a code, and an unknown code has to +// fail towards Internal rather than towards whatever it collides with. +func Code(c trogonerror.Code) connect.Code { + switch c { + case trogonerror.CodeCancelled: + return connect.CodeCanceled + case trogonerror.CodeUnknown: + return connect.CodeUnknown + case trogonerror.CodeInvalidArgument: + return connect.CodeInvalidArgument + case trogonerror.CodeDeadlineExceeded: + return connect.CodeDeadlineExceeded + case trogonerror.CodeNotFound: + return connect.CodeNotFound + case trogonerror.CodeAlreadyExists: + return connect.CodeAlreadyExists + case trogonerror.CodePermissionDenied: + return connect.CodePermissionDenied + case trogonerror.CodeResourceExhausted: + return connect.CodeResourceExhausted + case trogonerror.CodeFailedPrecondition: + return connect.CodeFailedPrecondition + case trogonerror.CodeAborted: + return connect.CodeAborted + case trogonerror.CodeOutOfRange: + return connect.CodeOutOfRange + case trogonerror.CodeUnimplemented: + return connect.CodeUnimplemented + case trogonerror.CodeInternal: + return connect.CodeInternal + case trogonerror.CodeUnavailable: + return connect.CodeUnavailable + case trogonerror.CodeDataLoss: + return connect.CodeDataLoss + case trogonerror.CodeUnauthenticated: + return connect.CodeUnauthenticated + default: + return connect.CodeInternal + } +} diff --git a/errconnect/code_test.go b/errconnect/code_test.go new file mode 100644 index 0000000..e0d1637 --- /dev/null +++ b/errconnect/code_test.go @@ -0,0 +1,47 @@ +package errconnect_test + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/stretchr/testify/assert" + + "github.com/TrogonStack/trogonerror" + "github.com/TrogonStack/trogonerror/errconnect" +) + +func TestCode(t *testing.T) { + tests := []struct { + name string + code trogonerror.Code + want connect.Code + }{ + {"CANCELLED", trogonerror.CodeCancelled, connect.CodeCanceled}, + {"UNKNOWN", trogonerror.CodeUnknown, connect.CodeUnknown}, + {"INVALID_ARGUMENT", trogonerror.CodeInvalidArgument, connect.CodeInvalidArgument}, + {"DEADLINE_EXCEEDED", trogonerror.CodeDeadlineExceeded, connect.CodeDeadlineExceeded}, + {"NOT_FOUND", trogonerror.CodeNotFound, connect.CodeNotFound}, + {"ALREADY_EXISTS", trogonerror.CodeAlreadyExists, connect.CodeAlreadyExists}, + {"PERMISSION_DENIED", trogonerror.CodePermissionDenied, connect.CodePermissionDenied}, + {"RESOURCE_EXHAUSTED", trogonerror.CodeResourceExhausted, connect.CodeResourceExhausted}, + {"FAILED_PRECONDITION", trogonerror.CodeFailedPrecondition, connect.CodeFailedPrecondition}, + {"ABORTED", trogonerror.CodeAborted, connect.CodeAborted}, + {"OUT_OF_RANGE", trogonerror.CodeOutOfRange, connect.CodeOutOfRange}, + {"UNIMPLEMENTED", trogonerror.CodeUnimplemented, connect.CodeUnimplemented}, + {"INTERNAL", trogonerror.CodeInternal, connect.CodeInternal}, + {"UNAVAILABLE", trogonerror.CodeUnavailable, connect.CodeUnavailable}, + {"DATA_LOSS", trogonerror.CodeDataLoss, connect.CodeDataLoss}, + {"UNAUTHENTICATED", trogonerror.CodeUnauthenticated, connect.CodeUnauthenticated}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, errconnect.Code(tt.code)) + }) + } + + t.Run("unknown code falls back to Internal", func(t *testing.T) { + var unknownCode trogonerror.Code = 999 + assert.Equal(t, connect.CodeInternal, errconnect.Code(unknownCode)) + }) +} diff --git a/go.mod b/go.mod index eddc872..4a6ffec 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,14 @@ module github.com/TrogonStack/trogonerror go 1.24.2 -require github.com/stretchr/testify v1.11.1 +require ( + connectrpc.com/connect v1.19.2 + github.com/stretchr/testify v1.11.1 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c4c1710..171f90f 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ +connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo= +connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= 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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=