1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package errors
16
17 import (
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "reflect"
22 "strings"
23 )
24
25
26 var DefaultHTTPCode = http.StatusUnprocessableEntity
27
28
29 type Error interface {
30 error
31 Code() int32
32 }
33
34 type apiError struct {
35 code int32
36 message string
37 }
38
39 func (a *apiError) Error() string {
40 return a.message
41 }
42
43 func (a *apiError) Code() int32 {
44 return a.code
45 }
46
47
48 func (a apiError) MarshalJSON() ([]byte, error) {
49 return json.Marshal(map[string]interface{}{
50 "code": a.code,
51 "message": a.message,
52 })
53 }
54
55
56 func New(code int32, message string, args ...interface{}) Error {
57 if len(args) > 0 {
58 return &apiError{
59 code: code,
60 message: fmt.Sprintf(message, args...),
61 }
62 }
63 return &apiError{
64 code: code,
65 message: message,
66 }
67 }
68
69
70 func NotFound(message string, args ...interface{}) Error {
71 if message == "" {
72 message = "Not found"
73 }
74 return New(http.StatusNotFound, fmt.Sprintf(message, args...))
75 }
76
77
78 func NotImplemented(message string) Error {
79 return New(http.StatusNotImplemented, message)
80 }
81
82
83 type MethodNotAllowedError struct {
84 code int32
85 Allowed []string
86 message string
87 }
88
89 func (m *MethodNotAllowedError) Error() string {
90 return m.message
91 }
92
93
94 func (m *MethodNotAllowedError) Code() int32 {
95 return m.code
96 }
97
98
99 func (m MethodNotAllowedError) MarshalJSON() ([]byte, error) {
100 return json.Marshal(map[string]interface{}{
101 "code": m.code,
102 "message": m.message,
103 "allowed": m.Allowed,
104 })
105 }
106
107 func errorAsJSON(err Error) []byte {
108
109 b, _ := json.Marshal(struct {
110 Code int32 `json:"code"`
111 Message string `json:"message"`
112 }{err.Code(), err.Error()})
113 return b
114 }
115
116 func flattenComposite(errs *CompositeError) *CompositeError {
117 var res []error
118 for _, er := range errs.Errors {
119 switch e := er.(type) {
120 case *CompositeError:
121 if e != nil && len(e.Errors) > 0 {
122 flat := flattenComposite(e)
123 if len(flat.Errors) > 0 {
124 res = append(res, flat.Errors...)
125 }
126 }
127 default:
128 if e != nil {
129 res = append(res, e)
130 }
131 }
132 }
133 return CompositeValidationError(res...)
134 }
135
136
137 func MethodNotAllowed(requested string, allow []string) Error {
138 msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
139 return &MethodNotAllowedError{
140 code: http.StatusMethodNotAllowed,
141 Allowed: allow,
142 message: msg,
143 }
144 }
145
146
147 func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
148 rw.Header().Set("Content-Type", "application/json")
149 switch e := err.(type) {
150 case *CompositeError:
151 er := flattenComposite(e)
152
153 if len(er.Errors) > 0 {
154 ServeError(rw, r, er.Errors[0])
155 } else {
156
157 ServeError(rw, r, nil)
158 }
159 case *MethodNotAllowedError:
160 rw.Header().Add("Allow", strings.Join(e.Allowed, ","))
161 rw.WriteHeader(asHTTPCode(int(e.Code())))
162 if r == nil || r.Method != http.MethodHead {
163 _, _ = rw.Write(errorAsJSON(e))
164 }
165 case Error:
166 value := reflect.ValueOf(e)
167 if value.Kind() == reflect.Ptr && value.IsNil() {
168 rw.WriteHeader(http.StatusInternalServerError)
169 _, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
170 return
171 }
172 rw.WriteHeader(asHTTPCode(int(e.Code())))
173 if r == nil || r.Method != http.MethodHead {
174 _, _ = rw.Write(errorAsJSON(e))
175 }
176 case nil:
177 rw.WriteHeader(http.StatusInternalServerError)
178 _, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
179 default:
180 rw.WriteHeader(http.StatusInternalServerError)
181 if r == nil || r.Method != http.MethodHead {
182 _, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
183 }
184 }
185 }
186
187 func asHTTPCode(input int) int {
188 if input >= 600 {
189 return DefaultHTTPCode
190 }
191 return input
192 }
193
View as plain text