Skip to content

Commit

Permalink
add stack-trace to problem-details error
Browse files Browse the repository at this point in the history
  • Loading branch information
meysamhadeli committed Oct 9, 2022
1 parent 43cd435 commit ffe1d59
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 65 deletions.
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Our problem details response body and headers will be look like this:
"title": "bad-request", // A short human-readable problem summary
"detail": "We have a bad request in our endpoint", // A human-readable explanation for what exactly happened
"type": "https://httpstatuses.io/400", // URI reference to identify the problem type
"instance": "/sample1" // URI reference of the occurrence
"instance": "/sample1", // URI reference of the occurrence
"stackTrace": "some more trace for error", // More trace information error for what exactly happened
}
```
```go
Expand Down Expand Up @@ -75,7 +76,11 @@ func sample1(c echo.Context) error {
```go
// problem details handler config
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
return problem.New(http.StatusUnauthorized, "unauthorized", err.Error())
return &problem.ProblemDetail{
Status: http.StatusUnauthorized,
Title: "unauthorized",
Detail: error.Error(),
}
})
```
#### Map Custom Type Error:
Expand All @@ -92,7 +97,11 @@ func sample2(c echo.Context) error {
```go
// problem details handler config
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
return problem.New(http.StatusBadRequest, "bad request", error.Error())
return &problem.ProblemDetail{
Status: http.StatusBadRequest,
Title: "bad request",
Detail: error.Error(),
}
})
```

Expand Down Expand Up @@ -132,7 +141,11 @@ func sample1(c *gin.Context) {
```go
// problem details handler config
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
return problem.New(http.StatusUnauthorized, "unauthorized", err.Error())
return &problem.ProblemDetail{
Status: http.StatusUnauthorized,
Title: "unauthorized",
Detail: err.Error(),
}
})
```
#### Map Custom Type Error:
Expand All @@ -151,7 +164,11 @@ func sample2(c *gin.Context) {
```go
// problem details handler config
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
return problem.New(http.StatusBadRequest, "bad request", error.Error())
return &problem.ProblemDetail{
Status: http.StatusBadRequest,
Title: "bad request",
Detail: err.Error(),
}
})
```

Expand All @@ -169,11 +186,15 @@ type CustomProblemDetail struct {
```go
// problem details handler config
problem.Map[custom_errors.ConflictError](func() problem.ProblemDetailErr {
return &custom_problems.CustomProblemDetail{
ProblemDetailErr: problem.New(http.StatusConflict, "conflict", error.Error()),
AdditionalInfo: "some additional info...",
Description: "some description...",
}
return &custom_problems.CustomProblemDetail{
ProblemDetailErr: &problem.ProblemDetail{
Status: http.StatusConflict,
Title: "conflict",
Detail: error.Error(),
},
AdditionalInfo: "some additional info...",
Description: "some description...",
}
})
```

Expand Down
48 changes: 22 additions & 26 deletions problem_details.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"reflect"
)

type problemDetail struct {
type ProblemDetail struct {
Status int `json:"status,omitempty"`
Title string `json:"title,omitempty"`
Detail string `json:"detail,omitempty"`
Expand Down Expand Up @@ -39,75 +39,63 @@ type ProblemDetailErr interface {
GetStackTrace() string
}

// New ProblemDetail Error
func New(status int, title string, detail string) ProblemDetailErr {
problemDetail := &problemDetail{
Status: status,
Title: title,
Detail: detail,
Type: getDefaultType(status),
}

return problemDetail
}

func (p *problemDetail) SetDetail(detail string) ProblemDetailErr {
func (p *ProblemDetail) SetDetail(detail string) ProblemDetailErr {
p.Detail = detail

return p
}

func (p *problemDetail) GetDetails() string {
func (p *ProblemDetail) GetDetails() string {
return p.Detail
}

func (p *problemDetail) SetStatus(status int) ProblemDetailErr {
func (p *ProblemDetail) SetStatus(status int) ProblemDetailErr {
p.Status = status

return p
}

func (p *problemDetail) GetStatus() int {
func (p *ProblemDetail) GetStatus() int {
return p.Status
}

func (p *problemDetail) SetTitle(title string) ProblemDetailErr {
func (p *ProblemDetail) SetTitle(title string) ProblemDetailErr {
p.Title = title

return p
}

func (p *problemDetail) GetTitle() string {
func (p *ProblemDetail) GetTitle() string {
return p.Title
}

func (p *problemDetail) SetType(typ string) ProblemDetailErr {
func (p *ProblemDetail) SetType(typ string) ProblemDetailErr {
p.Type = typ

return p
}

func (p *problemDetail) GetType() string {
func (p *ProblemDetail) GetType() string {
return p.Type
}

func (p *problemDetail) SetInstance(instance string) ProblemDetailErr {
func (p *ProblemDetail) SetInstance(instance string) ProblemDetailErr {
p.Instance = instance

return p
}

func (p *problemDetail) GetInstance() string {
func (p *ProblemDetail) GetInstance() string {
return p.Instance
}

func (p *problemDetail) SetStackTrace(stackTrace string) ProblemDetailErr {
func (p *ProblemDetail) SetStackTrace(stackTrace string) ProblemDetailErr {
p.StackTrace = stackTrace

return p
}

func (p *problemDetail) GetStackTrace() string {
func (p *ProblemDetail) GetStackTrace() string {
return p.StackTrace
}

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

func setDefaultProblemDetails(w http.ResponseWriter, r *http.Request, err error, statusCode int) (ProblemDetailErr, error) {
defaultProblem := func() ProblemDetailErr {
return &problemDetail{
return &ProblemDetail{
Type: getDefaultType(statusCode),
Status: statusCode,
Detail: err.Error(),
Expand Down Expand Up @@ -248,8 +236,16 @@ func validationProblems(problem ProblemDetailErr, err error, r *http.Request) {
if problem.GetTitle() == "" {
problem.SetTitle(http.StatusText(problem.GetStatus()))
}
if problem.GetStackTrace() == "" {
problem.SetStackTrace(errorsWithStack(err))
}
}

func getDefaultType(statusCode int) string {
return fmt.Sprintf("https://httpstatuses.io/%d", statusCode)
}

func errorsWithStack(err error) string {
res := fmt.Sprintf("%+v", err)
return res
}
53 changes: 34 additions & 19 deletions problem_details_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ import (
"testing"
)

func Test_BadRequest_Err(t *testing.T) {
badRequestErr := New(http.StatusBadRequest, "bad-request", "We have a bad request error")

assert.Equal(t, "We have a bad request error", badRequestErr.GetDetails())
assert.Equal(t, "bad-request", badRequestErr.GetTitle())
assert.Equal(t, "https://httpstatuses.io/400", badRequestErr.GetType())
assert.Equal(t, http.StatusBadRequest, badRequestErr.GetStatus())
}

func TestMap_CustomType_Echo(t *testing.T) {

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

Map[custom_errors.BadRequestError](func() ProblemDetailErr {
return New(http.StatusBadRequest, "bad-request", err.Error())
return &ProblemDetail{
Status: http.StatusBadRequest,
Title: "bad-request",
Detail: err.Error(),
}
})

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

Map[custom_errors.ConflictError](func() ProblemDetailErr {
return &CustomProblemDetailTest{
ProblemDetailErr: New(http.StatusConflict, "conflict", err.Error()),
AdditionalInfo: "some additional info...",
Description: "some description...",
ProblemDetailErr: &ProblemDetail{
Status: http.StatusConflict,
Title: "conflict",
Detail: err.Error(),
},
AdditionalInfo: "some additional info...",
Description: "some description...",
}
})

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

MapStatus(http.StatusBadGateway, func() ProblemDetailErr {
return New(http.StatusUnauthorized, "unauthorized", err.Error())
return &ProblemDetail{
Status: http.StatusUnauthorized,
Title: "unauthorized",
Detail: err.Error(),
}
})

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

Map[custom_errors.BadRequestError](func() ProblemDetailErr {
return New(http.StatusBadRequest, "bad-request", err.Error())
return &ProblemDetail{
Status: http.StatusBadRequest,
Title: "bad-request",
Detail: err.Error(),
}
})

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

Map[custom_errors.ConflictError](func() ProblemDetailErr {
return &CustomProblemDetailTest{
ProblemDetailErr: New(http.StatusConflict, "conflict", err.Error()),
AdditionalInfo: "some additional info...",
Description: "some description...",
ProblemDetailErr: &ProblemDetail{
Status: http.StatusConflict,
Title: "conflict",
Detail: err.Error(),
},
AdditionalInfo: "some additional info...",
Description: "some description...",
}
})

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

MapStatus(http.StatusBadGateway, func() ProblemDetailErr {
return New(http.StatusUnauthorized, "unauthorized", err.Error())
return &ProblemDetail{
Status: http.StatusUnauthorized,
Title: "unauthorized",
Detail: err.Error(),
}
})

p, _ := ResolveProblemDetails(w, req, err)
Expand Down
22 changes: 17 additions & 5 deletions samples/cmd/echo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,33 @@ func EchoErrorHandler(error error, c echo.Context) {

// map custom type error to problem details error
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
return problem.New(http.StatusBadRequest, "bad request", error.Error())
return &problem.ProblemDetail{
Status: http.StatusBadRequest,
Title: "bad request",
Detail: error.Error(),
}
})

// map custom type error to custom problem details error
problem.Map[custom_errors.ConflictError](func() problem.ProblemDetailErr {
return &custom_problems.CustomProblemDetail{
ProblemDetailErr: problem.New(http.StatusConflict, "conflict", error.Error()),
AdditionalInfo: "some additional info...",
Description: "some description...",
ProblemDetailErr: &problem.ProblemDetail{
Status: http.StatusConflict,
Title: "conflict",
Detail: error.Error(),
},
AdditionalInfo: "some additional info...",
Description: "some description...",
}
})

// map status code to problem details error
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
return problem.New(http.StatusUnauthorized, "unauthorized", error.Error())
return &problem.ProblemDetail{
Status: http.StatusUnauthorized,
Title: "unauthorized",
Detail: error.Error(),
}
})

// resolve problem details error from response in echo
Expand Down
22 changes: 17 additions & 5 deletions samples/cmd/gin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,33 @@ func GinErrorHandler() gin.HandlerFunc {

// map custom type error to problem details error
problem.Map[custom_errors.BadRequestError](func() problem.ProblemDetailErr {
return problem.New(http.StatusBadRequest, "bad request", err.Error())
return &problem.ProblemDetail{
Status: http.StatusBadRequest,
Title: "bad request",
Detail: err.Error(),
}
})

// map custom type error to custom problem details error
problem.Map[custom_errors.ConflictError](func() problem.ProblemDetailErr {
return &custom_problems.CustomProblemDetail{
ProblemDetailErr: problem.New(http.StatusConflict, "conflict", err.Error()),
AdditionalInfo: "some additional info...",
Description: "some description...",
ProblemDetailErr: &problem.ProblemDetail{
Status: http.StatusConflict,
Title: "conflict",
Detail: err.Error(),
},
AdditionalInfo: "some additional info...",
Description: "some description...",
}
})

// map status code to problem details error
problem.MapStatus(http.StatusBadGateway, func() problem.ProblemDetailErr {
return problem.New(http.StatusUnauthorized, "unauthorized", err.Error())
return &problem.ProblemDetail{
Status: http.StatusUnauthorized,
Title: "unauthorized",
Detail: err.Error(),
}
})

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

0 comments on commit ffe1d59

Please sign in to comment.