Skip to content

Commit c38d0fb

Browse files
authored
GRPC error codes, 3 new error types, auto detect context errors (#8)
[minor] added helpers for GRPC status codes and messages [minor] introduced 3 new error types NotImplemented, ContextCanceled, ContextTimedout [minor] context errors are automatically identified in error types [patch] improved test coverage [-] updated benchmark results & version in README
1 parent 25a09ef commit c38d0fb

File tree

11 files changed

+777
-425
lines changed

11 files changed

+777
-425
lines changed

.github/workflows/go.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ jobs:
3232
path-to-profile: covprofile
3333

3434
- name: golangci-lint
35-
uses: golangci/golangci-lint-action@v6
35+
uses: golangci/golangci-lint-action@v8
3636
with:
37-
version: v1.60
37+
version: v2.1

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#error-handling)
88
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/creativecreature/sturdyc/blob/master/LICENSE)
99

10-
# Errors v1.0.0
10+
# Errors v1.3.1
1111

1212
Errors package is a drop-in replacement of the built-in Go errors package. It lets you create errors of 11 different types,
1313
which should handle most of the use cases. Some of them are a bit too specific for web applications, but useful nonetheless.
@@ -219,30 +219,30 @@ And the `fmt.Println(err.Error())` generated output on stdout would be:
219219
/Users/username/go/src/errorscheck/main.go:28 /Users/username/go/src/errorscheck/main.go:20 sinking bar
220220
```
221221

222-
## Benchmark [2022-01-12]
222+
## Benchmark [2025-07-03]
223223

224-
MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports), 32 GB 3733 MHz LPDDR4X
224+
Macbook Air 13-inch, M3, 2024, Memory: 24 GB
225225

226226
```bash
227227
$ go version
228-
go version go1.19.5 darwin/amd64
228+
go version go1.24.4 darwin/arm64
229229

230230
$ go test -benchmem -bench .
231231
goos: darwin
232-
goarch: amd64
232+
goarch: arm64
233233
pkg: github.com/naughtygopher/errors
234-
cpu: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
235-
Benchmark_Internal-8 1526194 748.8 ns/op 1104 B/op 2 allocs/op
236-
Benchmark_Internalf-8 1281465 944.0 ns/op 1128 B/op 3 allocs/op
237-
Benchmark_InternalErr-8 1494351 806.7 ns/op 1104 B/op 2 allocs/op
238-
Benchmark_InternalGetError-8 981162 1189 ns/op 1528 B/op 6 allocs/op
239-
Benchmark_InternalGetErrorWithNestedError-8 896322 1267 ns/op 1544 B/op 6 allocs/op
240-
Benchmark_InternalGetMessage-8 1492812 804.2 ns/op 1104 B/op 2 allocs/op
241-
Benchmark_InternalGetMessageWithNestedError-8 1362092 886.3 ns/op 1128 B/op 3 allocs/op
242-
Benchmark_HTTPStatusCodeMessage-8 27494096 41.38 ns/op 16 B/op 1 allocs/op
243-
BenchmarkHasType-8 100000000 10.50 ns/op 0 B/op 0 allocs/op
234+
cpu: Apple M3
235+
Benchmark_Internal-8 3650916 321.7 ns/op 1104 B/op 2 allocs/op
236+
Benchmark_Internalf-8 3155463 378.9 ns/op 1128 B/op 3 allocs/op
237+
Benchmark_InternalErr-8 3866085 312.2 ns/op 1104 B/op 2 allocs/op
238+
Benchmark_InternalGetError-8 1983544 608.0 ns/op 1576 B/op 6 allocs/op
239+
Benchmark_InternalGetErrorWithNestedError-8 2419369 497.8 ns/op 1592 B/op 6 allocs/op
240+
Benchmark_InternalGetMessage-8 3815074 316.1 ns/op 1104 B/op 2 allocs/op
241+
Benchmark_InternalGetMessageWithNestedError-8 3470449 342.2 ns/op 1128 B/op 3 allocs/op
242+
Benchmark_HTTPStatusCodeMessage-8 40540940 29.12 ns/op 16 B/op 1 allocs/op
243+
BenchmarkHasType-8 100000000 11.44 ns/op 0 B/op 0 allocs/op
244244
PASS
245-
ok github.com/naughtygopher/errors 15.006s
245+
ok github.com/naughtygopher/errors 13.805s
246246
```
247247

248248
## Contributing

errors.go

Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"bytes"
66
"fmt"
77
"io"
8-
"net/http"
98
"runtime"
109
"strconv"
1110
"strings"
@@ -42,6 +41,15 @@ const (
4241
TypeSubscriptionExpired
4342
// TypeDownstreamDependencyTimedout is error type for when a request to a downstream dependent service times out
4443
TypeDownstreamDependencyTimedout
44+
// TypeNotImplemented is error type for when the requested function cannot be fullfilled because of incapability
45+
TypeNotImplemented
46+
// TypeContextTimedout is error type for when the Go context has timed out
47+
TypeContextTimedout
48+
// TypeContextCancelled is error type for when the Go context has been cancelled
49+
TypeContextCancelled
50+
)
51+
52+
const (
4553

4654
// DefaultMessage is the default user friendly message
4755
DefaultMessage = "unknown error occurred"
@@ -164,54 +172,11 @@ func (e *Error) Is(err error) bool {
164172
return o == e
165173
}
166174

167-
// HTTPStatusCode is a convenience method used to get the appropriate HTTP response status code for the respective error type
175+
// Deprecated: HTTPStatusCode is a convenience method used to get the appropriate
176+
// HTTP response status code for the respective error type.
177+
// deprecated to free the Error type from protocol specific features
168178
func (e *Error) HTTPStatusCode() int {
169-
status := http.StatusInternalServerError
170-
switch e.eType {
171-
case TypeValidation:
172-
{
173-
status = http.StatusUnprocessableEntity
174-
}
175-
case TypeInputBody:
176-
{
177-
status = http.StatusBadRequest
178-
}
179-
180-
case TypeDuplicate:
181-
{
182-
status = http.StatusConflict
183-
}
184-
185-
case TypeUnauthenticated:
186-
{
187-
status = http.StatusUnauthorized
188-
}
189-
case TypeUnauthorized:
190-
{
191-
status = http.StatusForbidden
192-
}
193-
194-
case TypeEmpty:
195-
{
196-
status = http.StatusGone
197-
}
198-
199-
case TypeNotFound:
200-
{
201-
status = http.StatusNotFound
202-
203-
}
204-
case TypeMaximumAttempts:
205-
{
206-
status = http.StatusTooManyRequests
207-
}
208-
case TypeSubscriptionExpired:
209-
{
210-
status = http.StatusPaymentRequired
211-
}
212-
}
213-
214-
return status
179+
return httpStatusCode(e.eType)
215180
}
216181

217182
// Type returns the error type as integer
@@ -339,14 +304,14 @@ func New(msg string) *Error {
339304
return newerr(nil, msg, defaultErrType, 3)
340305
}
341306

342-
func Newf(fromat string, args ...interface{}) *Error {
307+
func Newf(fromat string, args ...any) *Error {
343308
return newerrf(nil, defaultErrType, 4, fromat, args...)
344309
}
345310

346311
// Errorf is a convenience method to create a new instance of Error with formatted message
347312
// Important: %w directive is not supported, use fmt.Errorf if you're using the %w directive or
348313
// use Wrap/Wrapf to wrap an error.
349-
func Errorf(fromat string, args ...interface{}) *Error {
314+
func Errorf(fromat string, args ...any) *Error {
350315
return newerrf(nil, defaultErrType, 4, fromat, args...)
351316
}
352317

go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module github.com/naughtygopher/errors
22

3-
go 1.19
3+
go 1.23.0
4+
5+
toolchain go1.24.4
6+
7+
require google.golang.org/grpc v1.73.0
8+
9+
require golang.org/x/sys v0.33.0 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
3+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
4+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
5+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
6+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
7+
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
8+
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
9+
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
10+
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=

grpc.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package errors
2+
3+
import (
4+
"google.golang.org/grpc/codes"
5+
)
6+
7+
func grpcStatusCode(eT errType) codes.Code {
8+
status := codes.Unknown
9+
switch eT {
10+
case TypeInternal:
11+
{
12+
status = codes.Internal
13+
}
14+
case TypeValidation, TypeInputBody:
15+
{
16+
status = codes.InvalidArgument
17+
}
18+
case TypeDuplicate:
19+
{
20+
status = codes.AlreadyExists
21+
}
22+
case TypeUnauthenticated:
23+
{
24+
status = codes.Unauthenticated
25+
}
26+
case TypeUnauthorized:
27+
{
28+
status = codes.PermissionDenied
29+
}
30+
case TypeEmpty, TypeNotFound:
31+
{
32+
status = codes.NotFound
33+
}
34+
case TypeMaximumAttempts:
35+
{
36+
status = codes.ResourceExhausted
37+
}
38+
case TypeSubscriptionExpired:
39+
{
40+
status = codes.Unavailable
41+
}
42+
case TypeNotImplemented:
43+
{
44+
status = codes.Unimplemented
45+
}
46+
case TypeContextTimedout, TypeDownstreamDependencyTimedout:
47+
{
48+
status = codes.DeadlineExceeded
49+
}
50+
case TypeContextCancelled:
51+
{
52+
status = codes.Canceled
53+
}
54+
55+
}
56+
57+
return status
58+
}
59+
60+
// GRPCStatusCodeMessage returns the appropriate GRPC status code, message, boolean for the error
61+
// the boolean value is true if the error was of type *Error, false otherwise.
62+
func GRPCStatusCodeMessage(err error) (codes.Code, string, bool) {
63+
code, isErr := GRPCStatusCode(err)
64+
msg, isErrMsg := Message(err)
65+
if msg == "" {
66+
msg = err.Error()
67+
}
68+
return code, msg, isErr && isErrMsg
69+
}
70+
71+
// GRPCStatusCode returns appropriate GRPC response status code based on type of the error. The boolean
72+
// is 'true' if the provided error is of type *Err. If joined error, boolean is true if all joined errors
73+
// are of type *Error
74+
// In case of joined errors, it'll return the status code of the last *Error
75+
func GRPCStatusCode(err error) (codes.Code, bool) {
76+
derr, _ := err.(*Error)
77+
if derr != nil {
78+
return grpcStatusCode(derr.Type()), true
79+
}
80+
81+
// Since TypeInternal is the default returned by getErrType, it is ignored.
82+
if et := getErrType(err); et != TypeInternal {
83+
return grpcStatusCode(et), false
84+
}
85+
86+
jerr, _ := err.(*joinError)
87+
if jerr != nil {
88+
elen := len(jerr.errs)
89+
isErr := true
90+
for i := elen - 1; i >= 0; i-- {
91+
code, isE := GRPCStatusCode(jerr.errs[i])
92+
isErr = isE && isErr
93+
if isE {
94+
return code, isErr
95+
}
96+
}
97+
}
98+
99+
return codes.Unknown, false
100+
}

0 commit comments

Comments
 (0)