-
Notifications
You must be signed in to change notification settings - Fork 12
/
error.go
185 lines (157 loc) · 4.73 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package jsh
import (
"fmt"
"net/http"
"strings"
)
/*
DefaultError can be customized in order to provide a more customized error
Detail message when an Internal Server Error occurs. Optionally, you can modify
a returned jsh.Error before sending it as a response as well.
*/
var DefaultErrorDetail = "Request failed, something went wrong."
// DefaultTitle can be customized to provide a more customized ISE Title
var DefaultErrorTitle = "Internal Server Error"
/*
ErrorType represents the common interface requirements that libraries may
specify if they would like to accept either a single error or a list.
*/
type ErrorType interface {
// Error returns a formatted error and allows it to conform to the stdErr
// interface.
Error() string
// Validate checks that the error is valid in the context of JSONAPI
Validate(r *http.Request, response bool) *Error
// StatusCode returns the first encountered HTTP Status Code for the error type.
// Returns 0 if none is set.
StatusCode() int
}
// ErrorList is wraps an Error Array so that it can implement Sendable
type ErrorList []*Error
// Validate checks all errors within the list to ensure that they are valid
func (e ErrorList) Validate(r *http.Request, response bool) *Error {
for _, err := range e {
validationErr := err.Validate(r, response)
if validationErr != nil {
return validationErr
}
}
return nil
}
// Fulfills the default error interface
func (e ErrorList) Error() string {
var msg string
for _, err := range e {
msg += fmt.Sprintf("%s\n", err.Error())
}
return msg
}
/*
StatusCode (HTTP) of the first error in the list. Defaults to 0 if the list is
empty or one has not yet been set for the first error.
*/
func (e ErrorList) StatusCode() int {
if len(e) == 0 {
return 0
}
return e[0].Status
}
/*
Error consists of a number of contextual attributes to make conveying
certain error type simpler as per the JSON API specification:
http://jsonapi.org/format/#error-objects
error := &jsh.Error{
Title: "Authentication Failure",
Detail: "Category 4 Username Failure",
Status: 401
}
jsh.Send(w, r, error)
*/
type Error struct {
Title string `json:"title"`
Detail string `json:"detail"`
Status int `json:"status,string"`
Source struct {
Pointer string `json:"pointer"`
} `json:"source"`
ISE string `json:"-"`
}
/*
Error will print an internal server error if set, or default back to the SafeError()
format if not. As usual, err.Error() should not be considered safe for presentation
to the end user, use err.SafeError() instead.
*/
func (e *Error) Error() string {
msg := fmt.Sprintf("%d: %s - %s", e.Status, e.Title, e.Detail)
if e.Source.Pointer != "" {
msg += fmt.Sprintf("(Source.Pointer: %s)", e.Source.Pointer)
}
if e.ISE != "" {
msg += fmt.Sprintf("\nInternal Error: %s", e.ISE)
}
return msg
}
/*
Validate ensures that the an error meets all JSON API criteria.
*/
func (e *Error) Validate(r *http.Request, response bool) *Error {
switch {
case e.Status == 0:
return ISE(fmt.Sprintf("No HTTP Status set for error %+v\n", e))
case e.Status < 400 || e.Status > 600:
return ISE(fmt.Sprintf("HTTP Status out of valid range for error %+v\n", e))
case e.Status == 422 && e.Source.Pointer == "":
return ISE(fmt.Sprintf("Source Pointer must be set for 422 Status error"))
}
return nil
}
/*
StatusCode (HTTP) for the error. Defaults to 0.
*/
func (e *Error) StatusCode() int {
return e.Status
}
/*
ISE is a convenience function for creating a ready-to-go Internal Service Error
response. The message you pass in is set to the ErrorObject.ISE attribute so you
can gracefully log ISE's internally before sending them.
*/
func ISE(internalMessage string) *Error {
return &Error{
Title: DefaultErrorTitle,
Detail: DefaultErrorDetail,
Status: http.StatusInternalServerError,
ISE: internalMessage,
}
}
/*
InputError creates a properly formatted HTTP Status 422 error with an appropriate
user safe message. The parameter "attribute" will format err.Source.Pointer to be
"/data/attributes/<attribute>".
*/
func InputError(msg string, attribute string) *Error {
err := &Error{
Title: "Invalid Attribute",
Detail: msg,
Status: 422,
}
// Assign this after the fact, easier to do
err.Source.Pointer = fmt.Sprintf("/data/attributes/%s", strings.ToLower(attribute))
return err
}
// SpecificationError is used whenever the Client violates the JSON API Spec
func SpecificationError(detail string) *Error {
return &Error{
Title: "JSON API Specification Error",
Detail: detail,
Status: http.StatusNotAcceptable,
}
}
// NotFound returns a 404 formatted error
func NotFound(resourceType string, id string) *Error {
return &Error{
Title: "Not Found",
Detail: fmt.Sprintf("No resource of type '%s' exists for ID: %s", resourceType, id),
Status: http.StatusNotFound,
}
}