-
Notifications
You must be signed in to change notification settings - Fork 17
/
error.go
129 lines (115 loc) · 3.07 KB
/
error.go
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
129
package db
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"github.com/lib/pq"
"github.com/circleci/ex/o11y"
)
var (
ErrNop = o11y.NewWarning("no update or results")
ErrConstrained = errors.New("violates constraints")
ErrException = errors.New("exception")
ErrCanceled = o11y.NewWarning("statement canceled")
ErrBadConn = o11y.NewWarning("bad connection")
)
const (
pgForeignKeyConstraintErrorCode = "23503"
pgUniqueViolationErrorCode = "23505"
pgExceptionRaised = "P0001"
pgStatementCanceled = "57014"
)
func mapExecErrors(err error, res sql.Result) error {
found, err := mapError(err)
if found {
return err
}
if err != nil {
return err
}
rows, err := res.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return ErrNop
}
return nil
}
// mapError maps a few pq errors to errors defined in this package, some wrapping the original
// error. If a mapping was made the returned bool will be true, if not the original error is returned and
// the bool will be false.
func mapError(err error) (bool, error) {
if ok, e := mapBadCon(err); ok {
return true, e
}
e := &pq.Error{}
if errors.As(err, &e) {
switch e.Code {
case pgForeignKeyConstraintErrorCode:
return true, &Error{sentinel: ErrConstrained, pqErr: e}
case pgExceptionRaised:
return true, &Error{sentinel: ErrException, pqErr: e}
case pgStatementCanceled:
return true, &Error{sentinel: ErrCanceled, pqErr: e}
case pgUniqueViolationErrorCode:
return true, &Error{sentinel: ErrNop, pqErr: e}
}
return true, &Error{pqErr: e}
}
return false, err
}
func mapBadCon(err error) (bool, error) {
if errors.Is(err, driver.ErrBadConn) {
return true, ErrBadConn
}
return false, err
}
func badConn(err error) bool {
return errors.Is(err, ErrBadConn)
}
// Error wraps a pq.Error to make it available to the caller.
// The sentinel is included for easier testing of the existing error vars.
// for example errors.Is(err, ErrConstrained)
type Error struct {
pqErr *pq.Error
sentinel error
}
// PqError will return any wrapped pqError is e has one
func (e Error) PqError() *pq.Error {
return e.pqErr
}
// Is checks that this error is being checked for the special o11y error that is not
// added to the trace as an error. If the error is due to relatively expected failure response codes
// return true so it does not appear in the traces as an error.
func (e *Error) Is(target error) bool {
if e == nil {
return false
}
if o11y.IsWarningNoUnwrap(target) {
return o11y.IsWarning(e.sentinel)
}
return errors.Is(target, e.sentinel)
}
// Error returns the standard sentinel error format and then the underlying pq.Error
// if one exists
func (e *Error) Error() string {
if e.sentinel != nil {
if e.pqErr != nil {
return fmt.Sprintf("%s: %s - %s", e.sentinel.Error(), e.pqErr.Message, e.pqErr.Detail)
}
return e.sentinel.Error()
}
if e.pqErr != nil {
return e.pqErr.Error()
}
return "unknown database error"
}
func PqError(err error) *pq.Error {
e := &Error{}
if errors.As(err, &e) {
return e.pqErr
}
return nil
}