Skip to content

Commit ffe1d59

Browse files
committed
add stack-trace to problem-details error
1 parent 43cd435 commit ffe1d59

File tree

5 files changed

+121
-65
lines changed

5 files changed

+121
-65
lines changed

README.md

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Our problem details response body and headers will be look like this:
2323
"title": "bad-request", // A short human-readable problem summary
2424
"detail": "We have a bad request in our endpoint", // A human-readable explanation for what exactly happened
2525
"type": "https://httpstatuses.io/400", // URI reference to identify the problem type
26-
"instance": "/sample1" // URI reference of the occurrence
26+
"instance": "/sample1", // URI reference of the occurrence
27+
"stackTrace": "some more trace for error", // More trace information error for what exactly happened
2728
}
2829
```
2930
```go
@@ -75,7 +76,11 @@ func sample1(c echo.Context) error {
7576
```go
7677
// problem details handler config
7778
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
78-
return problem.New(http.StatusUnauthorized, "unauthorized", err.Error())
79+
return &problem.ProblemDetail{
80+
Status: http.StatusUnauthorized,
81+
Title: "unauthorized",
82+
Detail: error.Error(),
83+
}
7984
})
8085
```
8186
#### Map Custom Type Error:
@@ -92,7 +97,11 @@ func sample2(c echo.Context) error {
9297
```go
9398
// problem details handler config
9499
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
95-
return problem.New(http.StatusBadRequest, "bad request", error.Error())
100+
return &problem.ProblemDetail{
101+
Status: http.StatusBadRequest,
102+
Title: "bad request",
103+
Detail: error.Error(),
104+
}
96105
})
97106
```
98107

@@ -132,7 +141,11 @@ func sample1(c *gin.Context) {
132141
```go
133142
// problem details handler config
134143
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
135-
return problem.New(http.StatusUnauthorized, "unauthorized", err.Error())
144+
return &problem.ProblemDetail{
145+
Status: http.StatusUnauthorized,
146+
Title: "unauthorized",
147+
Detail: err.Error(),
148+
}
136149
})
137150
```
138151
#### Map Custom Type Error:
@@ -151,7 +164,11 @@ func sample2(c *gin.Context) {
151164
```go
152165
// problem details handler config
153166
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
154-
return problem.New(http.StatusBadRequest, "bad request", error.Error())
167+
return &problem.ProblemDetail{
168+
Status: http.StatusBadRequest,
169+
Title: "bad request",
170+
Detail: err.Error(),
171+
}
155172
})
156173
```
157174

@@ -169,11 +186,15 @@ type CustomProblemDetail struct {
169186
```go
170187
// problem details handler config
171188
problem.Map[custom_errors.ConflictError](func() problem.ProblemDetailErr {
172-
return &custom_problems.CustomProblemDetail{
173-
ProblemDetailErr: problem.New(http.StatusConflict, "conflict", error.Error()),
174-
AdditionalInfo: "some additional info...",
175-
Description: "some description...",
176-
}
189+
return &custom_problems.CustomProblemDetail{
190+
ProblemDetailErr: &problem.ProblemDetail{
191+
Status: http.StatusConflict,
192+
Title: "conflict",
193+
Detail: error.Error(),
194+
},
195+
AdditionalInfo: "some additional info...",
196+
Description: "some description...",
197+
}
177198
})
178199
```
179200

problem_details.go

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"reflect"
1212
)
1313

14-
type problemDetail struct {
14+
type ProblemDetail struct {
1515
Status int `json:"status,omitempty"`
1616
Title string `json:"title,omitempty"`
1717
Detail string `json:"detail,omitempty"`
@@ -39,75 +39,63 @@ type ProblemDetailErr interface {
3939
GetStackTrace() string
4040
}
4141

42-
// New ProblemDetail Error
43-
func New(status int, title string, detail string) ProblemDetailErr {
44-
problemDetail := &problemDetail{
45-
Status: status,
46-
Title: title,
47-
Detail: detail,
48-
Type: getDefaultType(status),
49-
}
50-
51-
return problemDetail
52-
}
53-
54-
func (p *problemDetail) SetDetail(detail string) ProblemDetailErr {
42+
func (p *ProblemDetail) SetDetail(detail string) ProblemDetailErr {
5543
p.Detail = detail
5644

5745
return p
5846
}
5947

60-
func (p *problemDetail) GetDetails() string {
48+
func (p *ProblemDetail) GetDetails() string {
6149
return p.Detail
6250
}
6351

64-
func (p *problemDetail) SetStatus(status int) ProblemDetailErr {
52+
func (p *ProblemDetail) SetStatus(status int) ProblemDetailErr {
6553
p.Status = status
6654

6755
return p
6856
}
6957

70-
func (p *problemDetail) GetStatus() int {
58+
func (p *ProblemDetail) GetStatus() int {
7159
return p.Status
7260
}
7361

74-
func (p *problemDetail) SetTitle(title string) ProblemDetailErr {
62+
func (p *ProblemDetail) SetTitle(title string) ProblemDetailErr {
7563
p.Title = title
7664

7765
return p
7866
}
7967

80-
func (p *problemDetail) GetTitle() string {
68+
func (p *ProblemDetail) GetTitle() string {
8169
return p.Title
8270
}
8371

84-
func (p *problemDetail) SetType(typ string) ProblemDetailErr {
72+
func (p *ProblemDetail) SetType(typ string) ProblemDetailErr {
8573
p.Type = typ
8674

8775
return p
8876
}
8977

90-
func (p *problemDetail) GetType() string {
78+
func (p *ProblemDetail) GetType() string {
9179
return p.Type
9280
}
9381

94-
func (p *problemDetail) SetInstance(instance string) ProblemDetailErr {
82+
func (p *ProblemDetail) SetInstance(instance string) ProblemDetailErr {
9583
p.Instance = instance
9684

9785
return p
9886
}
9987

100-
func (p *problemDetail) GetInstance() string {
88+
func (p *ProblemDetail) GetInstance() string {
10189
return p.Instance
10290
}
10391

104-
func (p *problemDetail) SetStackTrace(stackTrace string) ProblemDetailErr {
92+
func (p *ProblemDetail) SetStackTrace(stackTrace string) ProblemDetailErr {
10593
p.StackTrace = stackTrace
10694

10795
return p
10896
}
10997

110-
func (p *problemDetail) GetStackTrace() string {
98+
func (p *ProblemDetail) GetStackTrace() string {
11199
return p.StackTrace
112100
}
113101

@@ -217,7 +205,7 @@ func setMapStatusCode(w http.ResponseWriter, r *http.Request, err error, statusC
217205

218206
func setDefaultProblemDetails(w http.ResponseWriter, r *http.Request, err error, statusCode int) (ProblemDetailErr, error) {
219207
defaultProblem := func() ProblemDetailErr {
220-
return &problemDetail{
208+
return &ProblemDetail{
221209
Type: getDefaultType(statusCode),
222210
Status: statusCode,
223211
Detail: err.Error(),
@@ -248,8 +236,16 @@ func validationProblems(problem ProblemDetailErr, err error, r *http.Request) {
248236
if problem.GetTitle() == "" {
249237
problem.SetTitle(http.StatusText(problem.GetStatus()))
250238
}
239+
if problem.GetStackTrace() == "" {
240+
problem.SetStackTrace(errorsWithStack(err))
241+
}
251242
}
252243

253244
func getDefaultType(statusCode int) string {
254245
return fmt.Sprintf("https://httpstatuses.io/%d", statusCode)
255246
}
247+
248+
func errorsWithStack(err error) string {
249+
res := fmt.Sprintf("%+v", err)
250+
return res
251+
}

problem_details_test.go

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,6 @@ import (
1111
"testing"
1212
)
1313

14-
func Test_BadRequest_Err(t *testing.T) {
15-
badRequestErr := New(http.StatusBadRequest, "bad-request", "We have a bad request error")
16-
17-
assert.Equal(t, "We have a bad request error", badRequestErr.GetDetails())
18-
assert.Equal(t, "bad-request", badRequestErr.GetTitle())
19-
assert.Equal(t, "https://httpstatuses.io/400", badRequestErr.GetType())
20-
assert.Equal(t, http.StatusBadRequest, badRequestErr.GetStatus())
21-
}
22-
2314
func TestMap_CustomType_Echo(t *testing.T) {
2415

2516
e := echo.New()
@@ -30,7 +21,11 @@ func TestMap_CustomType_Echo(t *testing.T) {
3021
err := echo_endpoint1(c)
3122

3223
Map[custom_errors.BadRequestError](func() ProblemDetailErr {
33-
return New(http.StatusBadRequest, "bad-request", err.Error())
24+
return &ProblemDetail{
25+
Status: http.StatusBadRequest,
26+
Title: "bad-request",
27+
Detail: err.Error(),
28+
}
3429
})
3530

3631
p, _ := ResolveProblemDetails(c.Response(), c.Request(), err)
@@ -53,9 +48,13 @@ func TestMap_Custom_Problem_Err_Echo(t *testing.T) {
5348

5449
Map[custom_errors.ConflictError](func() ProblemDetailErr {
5550
return &CustomProblemDetailTest{
56-
ProblemDetailErr: New(http.StatusConflict, "conflict", err.Error()),
57-
AdditionalInfo: "some additional info...",
58-
Description: "some description...",
51+
ProblemDetailErr: &ProblemDetail{
52+
Status: http.StatusConflict,
53+
Title: "conflict",
54+
Detail: err.Error(),
55+
},
56+
AdditionalInfo: "some additional info...",
57+
Description: "some description...",
5958
}
6059
})
6160

@@ -81,7 +80,11 @@ func TestMap_Status_Echo(t *testing.T) {
8180
err := echo_endpoint2(c)
8281

8382
MapStatus(http.StatusBadGateway, func() ProblemDetailErr {
84-
return New(http.StatusUnauthorized, "unauthorized", err.Error())
83+
return &ProblemDetail{
84+
Status: http.StatusUnauthorized,
85+
Title: "unauthorized",
86+
Detail: err.Error(),
87+
}
8588
})
8689

8790
p, _ := ResolveProblemDetails(c.Response(), c.Request(), err)
@@ -130,7 +133,11 @@ func TestMap_CustomType_Gin(t *testing.T) {
130133
for _, err := range c.Errors {
131134

132135
Map[custom_errors.BadRequestError](func() ProblemDetailErr {
133-
return New(http.StatusBadRequest, "bad-request", err.Error())
136+
return &ProblemDetail{
137+
Status: http.StatusBadRequest,
138+
Title: "bad-request",
139+
Detail: err.Error(),
140+
}
134141
})
135142

136143
p, _ := ResolveProblemDetails(w, req, err)
@@ -162,9 +169,13 @@ func TestMap_Custom_Problem_Err_Gin(t *testing.T) {
162169

163170
Map[custom_errors.ConflictError](func() ProblemDetailErr {
164171
return &CustomProblemDetailTest{
165-
ProblemDetailErr: New(http.StatusConflict, "conflict", err.Error()),
166-
AdditionalInfo: "some additional info...",
167-
Description: "some description...",
172+
ProblemDetailErr: &ProblemDetail{
173+
Status: http.StatusConflict,
174+
Title: "conflict",
175+
Detail: err.Error(),
176+
},
177+
AdditionalInfo: "some additional info...",
178+
Description: "some description...",
168179
}
169180
})
170181

@@ -198,7 +209,11 @@ func TestMap_Status_Gin(t *testing.T) {
198209
for _, err := range c.Errors {
199210

200211
MapStatus(http.StatusBadGateway, func() ProblemDetailErr {
201-
return New(http.StatusUnauthorized, "unauthorized", err.Error())
212+
return &ProblemDetail{
213+
Status: http.StatusUnauthorized,
214+
Title: "unauthorized",
215+
Detail: err.Error(),
216+
}
202217
})
203218

204219
p, _ := ResolveProblemDetails(w, req, err)

samples/cmd/echo/main.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,33 @@ func EchoErrorHandler(error error, c echo.Context) {
4646

4747
// map custom type error to problem details error
4848
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
49-
return problem.New(http.StatusBadRequest, "bad request", error.Error())
49+
return &problem.ProblemDetail{
50+
Status: http.StatusBadRequest,
51+
Title: "bad request",
52+
Detail: error.Error(),
53+
}
5054
})
5155

5256
// map custom type error to custom problem details error
5357
problem.Map[custom_errors.ConflictError](func() problem.ProblemDetailErr {
5458
return &custom_problems.CustomProblemDetail{
55-
ProblemDetailErr: problem.New(http.StatusConflict, "conflict", error.Error()),
56-
AdditionalInfo: "some additional info...",
57-
Description: "some description...",
59+
ProblemDetailErr: &problem.ProblemDetail{
60+
Status: http.StatusConflict,
61+
Title: "conflict",
62+
Detail: error.Error(),
63+
},
64+
AdditionalInfo: "some additional info...",
65+
Description: "some description...",
5866
}
5967
})
6068

6169
// map status code to problem details error
6270
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
63-
return problem.New(http.StatusUnauthorized, "unauthorized", error.Error())
71+
return &problem.ProblemDetail{
72+
Status: http.StatusUnauthorized,
73+
Title: "unauthorized",
74+
Detail: error.Error(),
75+
}
6476
})
6577

6678
// resolve problem details error from response in echo

samples/cmd/gin/main.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,33 @@ func GinErrorHandler() gin.HandlerFunc {
5555

5656
// map custom type error to problem details error
5757
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
58-
return problem.New(http.StatusBadRequest, "bad request", err.Error())
58+
return &problem.ProblemDetail{
59+
Status: http.StatusBadRequest,
60+
Title: "bad request",
61+
Detail: err.Error(),
62+
}
5963
})
6064

6165
// map custom type error to custom problem details error
6266
problem.Map[custom_errors.ConflictError](func() problem.ProblemDetailErr {
6367
return &custom_problems.CustomProblemDetail{
64-
ProblemDetailErr: problem.New(http.StatusConflict, "conflict", err.Error()),
65-
AdditionalInfo: "some additional info...",
66-
Description: "some description...",
68+
ProblemDetailErr: &problem.ProblemDetail{
69+
Status: http.StatusConflict,
70+
Title: "conflict",
71+
Detail: err.Error(),
72+
},
73+
AdditionalInfo: "some additional info...",
74+
Description: "some description...",
6775
}
6876
})
6977

7078
// map status code to problem details error
7179
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
72-
return problem.New(http.StatusUnauthorized, "unauthorized", err.Error())
80+
return &problem.ProblemDetail{
81+
Status: http.StatusUnauthorized,
82+
Title: "unauthorized",
83+
Detail: err.Error(),
84+
}
7385
})
7486

7587
if _, err := problem.ResolveProblemDetails(c.Writer, c.Request, err); err != nil {

0 commit comments

Comments
 (0)