Skip to content

Commit 36e6b59

Browse files
authored
feat: add problem constructors for 401/403/409/422/500 (#23)
Closes #16
1 parent 3426d04 commit 36e6b59

4 files changed

Lines changed: 118 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ httpsuite.OK(w, user)
296296
httpsuite.OKWithMeta(w, users, httpsuite.NewPageMeta(page, pageSize, totalItems))
297297
httpsuite.Created(w, user, "/users/42")
298298
httpsuite.ProblemResponse(w, httpsuite.NewNotFoundProblem("user not found"))
299+
httpsuite.ProblemResponse(w, httpsuite.NewUnauthorizedProblem("missing token"))
300+
httpsuite.ProblemResponse(w, httpsuite.NewForbiddenProblem("admin only"))
301+
httpsuite.ProblemResponse(w, httpsuite.NewConflictProblem("email already taken"))
302+
httpsuite.ProblemResponse(w, httpsuite.NewUnprocessableProblem("order already shipped"))
303+
httpsuite.ProblemResponse(w, httpsuite.NewInternalProblem("unexpected error"))
299304
```
300305

301306
## Fluent helpers

‎problem_config.go‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ type ProblemConfig struct {
1414
func NewProblemConfig() ProblemConfig {
1515
return ProblemConfig{
1616
ErrorTypePaths: map[string]string{
17-
"validation_error": "/errors/validation-error",
18-
"not_found_error": "/errors/not-found",
19-
"server_error": "/errors/server-error",
20-
"bad_request_error": "/errors/bad-request",
17+
"validation_error": "/errors/validation-error",
18+
"not_found_error": "/errors/not-found",
19+
"server_error": "/errors/server-error",
20+
"bad_request_error": "/errors/bad-request",
21+
"unauthorized_error": "/errors/unauthorized",
22+
"forbidden_error": "/errors/forbidden",
23+
"conflict_error": "/errors/conflict",
24+
"unprocessable_error": "/errors/unprocessable-entity",
2125
},
2226
}
2327
}

‎problem_helpers.go‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,46 @@ func ProblemNotFound(detail string) *ProblemBuilder {
1818
Detail(detail)
1919
}
2020

21+
// ProblemUnauthorized returns an unauthorized problem builder.
22+
func ProblemUnauthorized(detail string) *ProblemBuilder {
23+
return Problem(http.StatusUnauthorized).
24+
Type(GetProblemTypeURL("unauthorized_error")).
25+
Title("Unauthorized").
26+
Detail(detail)
27+
}
28+
29+
// ProblemForbidden returns a forbidden problem builder.
30+
func ProblemForbidden(detail string) *ProblemBuilder {
31+
return Problem(http.StatusForbidden).
32+
Type(GetProblemTypeURL("forbidden_error")).
33+
Title("Forbidden").
34+
Detail(detail)
35+
}
36+
37+
// ProblemConflict returns a conflict problem builder.
38+
func ProblemConflict(detail string) *ProblemBuilder {
39+
return Problem(http.StatusConflict).
40+
Type(GetProblemTypeURL("conflict_error")).
41+
Title("Conflict").
42+
Detail(detail)
43+
}
44+
45+
// ProblemUnprocessable returns an unprocessable entity problem builder.
46+
func ProblemUnprocessable(detail string) *ProblemBuilder {
47+
return Problem(http.StatusUnprocessableEntity).
48+
Type(GetProblemTypeURL("unprocessable_error")).
49+
Title("Unprocessable Entity").
50+
Detail(detail)
51+
}
52+
53+
// ProblemInternal returns an internal server error problem builder.
54+
func ProblemInternal(detail string) *ProblemBuilder {
55+
return Problem(http.StatusInternalServerError).
56+
Type(GetProblemTypeURL("server_error")).
57+
Title("Internal Server Error").
58+
Detail(detail)
59+
}
60+
2161
// NewBadRequestProblem returns a ready-to-use bad request problem.
2262
func NewBadRequestProblem(detail string) *ProblemDetails {
2363
return ProblemBadRequest(detail).Build()
@@ -27,3 +67,28 @@ func NewBadRequestProblem(detail string) *ProblemDetails {
2767
func NewNotFoundProblem(detail string) *ProblemDetails {
2868
return ProblemNotFound(detail).Build()
2969
}
70+
71+
// NewUnauthorizedProblem returns a ready-to-use unauthorized problem.
72+
func NewUnauthorizedProblem(detail string) *ProblemDetails {
73+
return ProblemUnauthorized(detail).Build()
74+
}
75+
76+
// NewForbiddenProblem returns a ready-to-use forbidden problem.
77+
func NewForbiddenProblem(detail string) *ProblemDetails {
78+
return ProblemForbidden(detail).Build()
79+
}
80+
81+
// NewConflictProblem returns a ready-to-use conflict problem.
82+
func NewConflictProblem(detail string) *ProblemDetails {
83+
return ProblemConflict(detail).Build()
84+
}
85+
86+
// NewUnprocessableProblem returns a ready-to-use unprocessable entity problem.
87+
func NewUnprocessableProblem(detail string) *ProblemDetails {
88+
return ProblemUnprocessable(detail).Build()
89+
}
90+
91+
// NewInternalProblem returns a ready-to-use internal server error problem.
92+
func NewInternalProblem(detail string) *ProblemDetails {
93+
return ProblemInternal(detail).Build()
94+
}

‎problem_helpers_test.go‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@ func TestProblemBuilderHelpers(t *testing.T) {
1919
}
2020
}
2121

22+
func TestStatusProblemHelpers(t *testing.T) {
23+
t.Parallel()
24+
25+
tests := []struct {
26+
name string
27+
builder func(string) *ProblemBuilder
28+
direct func(string) *ProblemDetails
29+
status int
30+
title string
31+
errType string
32+
}{
33+
{"unauthorized", ProblemUnauthorized, NewUnauthorizedProblem, http.StatusUnauthorized, "Unauthorized", "unauthorized_error"},
34+
{"forbidden", ProblemForbidden, NewForbiddenProblem, http.StatusForbidden, "Forbidden", "forbidden_error"},
35+
{"conflict", ProblemConflict, NewConflictProblem, http.StatusConflict, "Conflict", "conflict_error"},
36+
{"unprocessable", ProblemUnprocessable, NewUnprocessableProblem, http.StatusUnprocessableEntity, "Unprocessable Entity", "unprocessable_error"},
37+
{"internal", ProblemInternal, NewInternalProblem, http.StatusInternalServerError, "Internal Server Error", "server_error"},
38+
}
39+
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
t.Parallel()
43+
44+
for _, got := range []*ProblemDetails{tt.builder("some detail").Build(), tt.direct("some detail")} {
45+
if got.Status != tt.status {
46+
t.Errorf("status = %d, want %d", got.Status, tt.status)
47+
}
48+
if got.Title != tt.title {
49+
t.Errorf("title = %q, want %q", got.Title, tt.title)
50+
}
51+
if got.Detail != "some detail" {
52+
t.Errorf("detail = %q", got.Detail)
53+
}
54+
if want := GetProblemTypeURL(tt.errType); got.Type != want || want == BlankURL {
55+
t.Errorf("type = %q, want %q", got.Type, want)
56+
}
57+
}
58+
})
59+
}
60+
}
61+
2262
func TestDirectProblemHelpers(t *testing.T) {
2363
t.Parallel()
2464

0 commit comments

Comments
 (0)