Skip to content

Commit e7d9a94

Browse files
committed
- add map for custom type error
- add map for status code - support create custom problem details
1 parent e0794a2 commit e7d9a94

File tree

6 files changed

+262
-234
lines changed

6 files changed

+262
-234
lines changed

problem_details.go

Lines changed: 170 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
"github.com/labstack/echo/v4"
88
"github.com/pkg/errors"
99
"net/http"
10+
"reflect"
1011
)
1112

12-
// ProblemDetail error struct
13-
type ProblemDetail struct {
13+
type problemDetail struct {
1414
Status int `json:"status,omitempty"`
1515
Title string `json:"title,omitempty"`
1616
Detail string `json:"detail,omitempty"`
@@ -19,26 +19,124 @@ type ProblemDetail struct {
1919
StackTrace string `json:"stackTrace,omitempty"`
2020
}
2121

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) {
23114

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
28128
}
29129

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
33133
}
34134

35135
// ResolveProblemDetails retrieve and resolve error with format problem details error
36136
func ResolveProblemDetails(w http.ResponseWriter, r *http.Request, err error) error {
37137

38138
var statusCode int = http.StatusInternalServerError
39-
40139
var echoError *echo.HTTPError
41-
42140
var ginError *gin.Error
43141

44142
if errors.As(err, &echoError) {
@@ -49,76 +147,97 @@ func ResolveProblemDetails(w http.ResponseWriter, r *http.Request, err error) er
49147
if rw.Written() {
50148
statusCode = rw.Status()
51149
}
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
53161
}
54162

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) {
56167

57-
if problem != nil {
58-
problem := problem()
168+
problemCustomType := mappers[reflect.TypeOf(err)]
169+
if problemCustomType != nil {
170+
prob := problemCustomType()
59171

60-
validationProblems(problem, err, statusCode, r)
172+
validationProblems(prob, err, r)
61173

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+
}
63183

184+
_, err = writeTo(w, prob)
64185
if err != nil {
65-
return err
186+
return err, false
66187
}
188+
return err, true
189+
}
190+
return err, false
191+
}
67192

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
69203
}
204+
return err, false
205+
}
70206

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{
73210
Type: getDefaultType(statusCode),
74211
Status: statusCode,
75212
Detail: err.Error(),
76213
Title: http.StatusText(statusCode),
77214
Instance: r.URL.RequestURI(),
78215
}
79216
}
80-
81-
_, err = defaultProblem().writeTo(w)
82-
217+
_, err = writeTo(w, defaultProblem())
83218
if err != nil {
84219
return err
85220
}
86-
87221
return err
88222
}
89223

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())
92226

93-
if problem.Status == 0 {
94-
problem.Status = statusCode
227+
if problem.GetStatus() == 0 {
228+
problem.SetStatus(http.StatusInternalServerError)
95229
}
96-
if problem.Instance == "" {
97-
problem.Instance = r.URL.RequestURI()
230+
if problem.GetInstance() == "" {
231+
problem.SetInstance(r.URL.RequestURI())
98232
}
99-
if problem.Type == "" {
100-
problem.Type = getDefaultType(problem.Status)
233+
if problem.GetType() == "" {
234+
problem.SetType(getDefaultType(problem.GetStatus()))
101235
}
102-
if problem.Title == "" {
103-
problem.Title = http.StatusText(problem.Status)
236+
if problem.GetTitle() == "" {
237+
problem.SetTitle(http.StatusText(problem.GetStatus()))
104238
}
105239
}
106240

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-
118241
func getDefaultType(statusCode int) string {
119242
return fmt.Sprintf("https://httpstatuses.io/%d", statusCode)
120243
}
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

Comments
 (0)