-
Notifications
You must be signed in to change notification settings - Fork 2
/
README.md.tmpl
128 lines (98 loc) · 4.26 KB
/
README.md.tmpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!-- File is generated by "github.com/hedhyw/semerr"; DO NOT EDIT. -->
# semerr
![Version](https://img.shields.io/github/v/tag/hedhyw/semerr)
![Build Status](https://github.com/hedhyw/semerr/actions/workflows/check.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/hedhyw/semerr)](https://goreportcard.com/report/github.com/hedhyw/semerr)
[![Coverage Status](https://coveralls.io/repos/github/hedhyw/semerr/badge.svg?branch=main)](https://coveralls.io/github/hedhyw/semerr?branch=main)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/hedhyw/semerr)](https://pkg.go.dev/github.com/hedhyw/semerr?tab=doc)
Package `semerr` helps to work with errors in Golang. It supports go 1.20 [errors.Join](https://pkg.go.dev/errors#Join).
<img alr="Go Bug" src="https://raw.githubusercontent.com/ashleymcnamara/gophers/master/GO_BUG.png" width="100px">
## Status errors
Those errors are based on HTTP status names, but they are designed to be
transport-independent. For example `semerr.NewNotFoundError(err)` indicates
that something is not found
(and it is possible to extract HTTP status -> `404` and gRPC status -> `5` if required).
Small example:
```go
// Repository layer.
type RedisUserRepo struct {}
func (r RedisUserRepo) Get(ctx context.Context, id string) (entity.User, error) {
u, err := r.client.Get(id)
switch {
case err == nil:
return u, nil
case errors.Is(err, redis.ErrNil):
return entity.User{}, semerr.NewNotFoundError(err)
default:
return entity.User{}, fmt.Errorf("getting user: %w", err)
}
}
// Domain layer.
func (c *Core) CreateOrder(ctx context.Context, order entity.Order) (err error)
user, err := c.userRepo.GetCurrentUser(ctx)
switch {
case err == nil:
// OK. Go on.
case errors.As(err, &semerr.NotFoundError{}):
// Repository can have any implementation and we should NOT know about
// `sql.ErrNoRows`, `redis.Nil`, `mongo.NoKey`, so we just compare the `err` to
// `semerr.NotFoundError`.
//
// We still can check `errors.Is(err, redis.Nil)` if we want,
// because the `err` is just wrapped without any modifications!
//
// Also we can change meaning by rewrapping the `err`. Check the next line:
return fmt.Errorf("getting user: %w", semerr.NewUnauthorizedError(err))
default:
return fmt.Errorf("getting user: %w" ,err)
}
// ...
}
// Transport layer.
func (s *Server) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
/* ... */
err := s.core.CreateOrder(ctx, order)
if err != nil {
// Respond with the correct status.
w.WriteHeader(httperr.Code(err))
// It is better to organize a helper for `err` responding.
return
}
w.WriteHeader(http.StatusOK)
}
```
## Mechanics
```go
errOriginal := errors.New("some error")
errWrapped := semerr.NewBadRequestError(errOriginal) // The text will be the same.
errJoined := errors.Join(errOriginal, errWrapped) // It supports joined errors.
fmt.Println(errWrapped) // "some error"
fmt.Println(httperr.Code(errWrapped)) // http.StatusBadRequest
fmt.Println(httperr.Code(errJoined)) // http.StatusBadRequest
fmt.Println(grpcerr.Code(errWrapped)) // codes.InvalidArgument
fmt.Println(grpcerr.Code(errJoined)) // codes.InvalidArgument
fmt.Println(errors.Is(err, errOriginal)) // true
fmt.Println(semerr.NewBadRequestError(nil)) // nil
fmt.Println(httperr.Wrap(errOriginal, http.StatusBadRequest)) // = semerr.NewBadRequestError(errOriginal)
```
## Const error
An error that can be defined as `const`.
```go
var errMutable error = errors.New("mutable error") // Do not like this?
const errImmutable semerr.Error = "immutable error" // So use this.
```
## Also see
```go
err := errors.New("some error")
{{- range $errorDef := . }}
{{ multlineComment $errorDef.Description }}
// HTTP: {{ httpStatusText $errorDef.HTTPStatus }} ({{ $errorDef.HTTPStatus }}); GRPC: {{ grpcStatusText $errorDef.GRPCStatus }} ({{ $errorDef.GRPCStatus }}).
err = semerr.New{{ $errorDef.Name }}(err)
{{- end }}
```
## Contributing
Pull requests are welcomed. If you want to add a new meaning error then
edit the file
[internal/cmd/generator/errors.yaml](internal/cmd/generator/errors.yaml)
and generate a new code, for this run `make`.