@@ -7,10 +7,10 @@ import (
7
7
"github.com/labstack/echo/v4"
8
8
"github.com/pkg/errors"
9
9
"net/http"
10
+ "reflect"
10
11
)
11
12
12
- // ProblemDetail error struct
13
- type ProblemDetail struct {
13
+ type problemDetail struct {
14
14
Status int `json:"status,omitempty"`
15
15
Title string `json:"title,omitempty"`
16
16
Detail string `json:"detail,omitempty"`
@@ -19,26 +19,124 @@ type ProblemDetail struct {
19
19
StackTrace string `json:"stackTrace,omitempty"`
20
20
}
21
21
22
- var mappers = map [int ]func () * ProblemDetail {}
22
+ var mappers = map [reflect.Type ]func () ProblemDetailErr {}
23
+ var mapperStatus = map [int ]func () ProblemDetailErr {}
24
+
25
+ // ProblemDetailErr ProblemDetail error interface
26
+ type ProblemDetailErr interface {
27
+ SetStatus (status int ) ProblemDetailErr
28
+ GetStatus () int
29
+ SetTitle (title string ) ProblemDetailErr
30
+ GetTitle () string
31
+ SetDetail (detail string ) ProblemDetailErr
32
+ GetDetails () string
33
+ SetType (typ string ) ProblemDetailErr
34
+ GetType () string
35
+ SetInstance (instance string ) ProblemDetailErr
36
+ GetInstance () string
37
+ SetStackTrace (stackTrace string ) ProblemDetailErr
38
+ GetStackTrace () string
39
+ }
40
+
41
+ // New ProblemDetail Error
42
+ func New (status int , title string , detail string ) ProblemDetailErr {
43
+ problemDetail := & problemDetail {
44
+ Status : status ,
45
+ Title : title ,
46
+ Detail : detail ,
47
+ Type : getDefaultType (status ),
48
+ }
49
+
50
+ return problemDetail
51
+ }
52
+
53
+ func (p * problemDetail ) SetDetail (detail string ) ProblemDetailErr {
54
+ p .Detail = detail
55
+
56
+ return p
57
+ }
58
+
59
+ func (p * problemDetail ) GetDetails () string {
60
+ return p .Detail
61
+ }
62
+
63
+ func (p * problemDetail ) SetStatus (status int ) ProblemDetailErr {
64
+ p .Status = status
65
+
66
+ return p
67
+ }
68
+
69
+ func (p * problemDetail ) GetStatus () int {
70
+ return p .Status
71
+ }
72
+
73
+ func (p * problemDetail ) SetTitle (title string ) ProblemDetailErr {
74
+ p .Title = title
75
+
76
+ return p
77
+ }
78
+
79
+ func (p * problemDetail ) GetTitle () string {
80
+ return p .Title
81
+ }
82
+
83
+ func (p * problemDetail ) SetType (typ string ) ProblemDetailErr {
84
+ p .Type = typ
85
+
86
+ return p
87
+ }
88
+
89
+ func (p * problemDetail ) GetType () string {
90
+ return p .Type
91
+ }
92
+
93
+ func (p * problemDetail ) SetInstance (instance string ) ProblemDetailErr {
94
+ p .Instance = instance
95
+
96
+ return p
97
+ }
98
+
99
+ func (p * problemDetail ) GetInstance () string {
100
+ return p .Instance
101
+ }
102
+
103
+ func (p * problemDetail ) SetStackTrace (stackTrace string ) ProblemDetailErr {
104
+ p .StackTrace = stackTrace
105
+
106
+ return p
107
+ }
108
+
109
+ func (p * problemDetail ) GetStackTrace () string {
110
+ return p .StackTrace
111
+ }
112
+
113
+ func writeTo (w http.ResponseWriter , p ProblemDetailErr ) (int , error ) {
23
114
24
- // WriteTo writes the JSON Problem to an HTTP Response Writer
25
- func (p * ProblemDetail ) writeTo (w http.ResponseWriter ) (int , error ) {
26
- p .writeHeaderTo (w )
27
- return w .Write (p .json ())
115
+ w .Header ().Set ("Content-Type" , "application/problem+json" )
116
+ w .WriteHeader (p .GetStatus ())
117
+
118
+ val , err := json .Marshal (p )
119
+ if err != nil {
120
+ return 0 , err
121
+ }
122
+ return w .Write (val )
123
+ }
124
+
125
+ // MapStatus map status code to problem details error
126
+ func MapStatus (statusCode int , funcProblem func () ProblemDetailErr ) {
127
+ mapperStatus [statusCode ] = funcProblem
28
128
}
29
129
30
- // Map map error to problem details error
31
- func Map ( statusCode int , funcProblem func () * ProblemDetail ) {
32
- mappers [statusCode ] = funcProblem
130
+ // Map map custom type error to problem details error
131
+ func Map [ T error ]( funcProblem func () ProblemDetailErr ) {
132
+ mappers [reflect . TypeOf ( * new ( T )) ] = funcProblem
33
133
}
34
134
35
135
// ResolveProblemDetails retrieve and resolve error with format problem details error
36
136
func ResolveProblemDetails (w http.ResponseWriter , r * http.Request , err error ) error {
37
137
38
138
var statusCode int = http .StatusInternalServerError
39
-
40
139
var echoError * echo.HTTPError
41
-
42
140
var ginError * gin.Error
43
141
44
142
if errors .As (err , & echoError ) {
@@ -49,76 +147,97 @@ func ResolveProblemDetails(w http.ResponseWriter, r *http.Request, err error) er
49
147
if rw .Written () {
50
148
statusCode = rw .Status ()
51
149
}
52
- err = err .(* gin.Error )
150
+ err = err .(* gin.Error ).Err .(error )
151
+ }
152
+
153
+ var mapCustomTypeErr , mapCustomType = setMapCustomType (w , r , err )
154
+ if mapCustomType {
155
+ return mapCustomTypeErr
156
+ }
157
+
158
+ var mapStatusErr , mapStatus = setMapStatusCode (w , r , err , statusCode )
159
+ if mapStatus {
160
+ return mapStatusErr
53
161
}
54
162
55
- problem := mappers [statusCode ]
163
+ return setDefaultProblemDetails (w , r , err , statusCode )
164
+ }
165
+
166
+ func setMapCustomType (w http.ResponseWriter , r * http.Request , err error ) (error , bool ) {
56
167
57
- if problem != nil {
58
- problem := problem ()
168
+ problemCustomType := mappers [reflect .TypeOf (err )]
169
+ if problemCustomType != nil {
170
+ prob := problemCustomType ()
59
171
60
- validationProblems (problem , err , statusCode , r )
172
+ validationProblems (prob , err , r )
61
173
62
- _ , err = problem .writeTo (w )
174
+ for k , v := range mapperStatus {
175
+ if k == prob .GetStatus () {
176
+ _ , err = writeTo (w , v ())
177
+ if err != nil {
178
+ return err , false
179
+ }
180
+ return err , true
181
+ }
182
+ }
63
183
184
+ _ , err = writeTo (w , prob )
64
185
if err != nil {
65
- return err
186
+ return err , false
66
187
}
188
+ return err , true
189
+ }
190
+ return err , false
191
+ }
67
192
68
- return err
193
+ func setMapStatusCode (w http.ResponseWriter , r * http.Request , err error , statusCode int ) (error , bool ) {
194
+ problemStatus := mapperStatus [statusCode ]
195
+ if problemStatus != nil {
196
+ prob := problemStatus ()
197
+ validationProblems (prob , err , r )
198
+ _ , err = writeTo (w , prob )
199
+ if err != nil {
200
+ return err , false
201
+ }
202
+ return err , true
69
203
}
204
+ return err , false
205
+ }
70
206
71
- defaultProblem := func () * ProblemDetail {
72
- return & ProblemDetail {
207
+ func setDefaultProblemDetails (w http.ResponseWriter , r * http.Request , err error , statusCode int ) error {
208
+ defaultProblem := func () ProblemDetailErr {
209
+ return & problemDetail {
73
210
Type : getDefaultType (statusCode ),
74
211
Status : statusCode ,
75
212
Detail : err .Error (),
76
213
Title : http .StatusText (statusCode ),
77
214
Instance : r .URL .RequestURI (),
78
215
}
79
216
}
80
-
81
- _ , err = defaultProblem ().writeTo (w )
82
-
217
+ _ , err = writeTo (w , defaultProblem ())
83
218
if err != nil {
84
219
return err
85
220
}
86
-
87
221
return err
88
222
}
89
223
90
- func validationProblems (problem * ProblemDetail , err error , statusCode int , r * http.Request ) {
91
- problem .Detail = err .Error ()
224
+ func validationProblems (problem ProblemDetailErr , err error , r * http.Request ) {
225
+ problem .SetDetail ( err .Error () )
92
226
93
- if problem .Status == 0 {
94
- problem .Status = statusCode
227
+ if problem .GetStatus () == 0 {
228
+ problem .SetStatus ( http . StatusInternalServerError )
95
229
}
96
- if problem .Instance == "" {
97
- problem .Instance = r .URL .RequestURI ()
230
+ if problem .GetInstance () == "" {
231
+ problem .SetInstance ( r .URL .RequestURI () )
98
232
}
99
- if problem .Type == "" {
100
- problem .Type = getDefaultType (problem .Status )
233
+ if problem .GetType () == "" {
234
+ problem .SetType ( getDefaultType (problem .GetStatus ()) )
101
235
}
102
- if problem .Title == "" {
103
- problem .Title = http .StatusText (problem .Status )
236
+ if problem .GetTitle () == "" {
237
+ problem .SetTitle ( http .StatusText (problem .GetStatus ()) )
104
238
}
105
239
}
106
240
107
- func (p * ProblemDetail ) writeHeaderTo (w http.ResponseWriter ) {
108
- w .Header ().Set ("Content-Type" , "application/problem+json" )
109
-
110
- w .WriteHeader (p .Status )
111
- }
112
-
113
- func (p * ProblemDetail ) json () []byte {
114
- res , _ := json .Marshal (& p )
115
- return res
116
- }
117
-
118
241
func getDefaultType (statusCode int ) string {
119
242
return fmt .Sprintf ("https://httpstatuses.io/%d" , statusCode )
120
243
}
121
-
122
- func getUrl (w http.ResponseWriter , r * http.Request ) {
123
- fmt .Printf ("Req: %s %s\n " , r .Host , r .URL .Path )
124
- }
0 commit comments