Skip to content

Commit 1580f0b

Browse files
committed
chore(lint): relinted code base according to strengthened rules.
Following the refactoring of error switch statements, added tests cases to cover all new code branches. NOTE: remember not to apply the auto fix for errlint, as it embarks into ambitious code rewrites that are broken. Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
1 parent b1b6a71 commit 1580f0b

File tree

8 files changed

+178
-90
lines changed

8 files changed

+178
-90
lines changed

api.go

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ package errors
55

66
import (
77
"encoding/json"
8+
"errors"
89
"fmt"
910
"net/http"
1011
"reflect"
1112
"strings"
1213
)
1314

1415
// DefaultHTTPCode is used when the error Code cannot be used as an HTTP code.
16+
//
17+
//nolint:gochecknoglobals // it should have been a constant in the first place, but now it is mutable so we have to leave it here or introduce a breaking change.
1518
var DefaultHTTPCode = http.StatusUnprocessableEntity
1619

17-
// Error represents a error interface all swagger framework errors implement
20+
// Error represents a error interface all swagger framework errors implement.
1821
type Error interface {
1922
error
2023
Code() int32
@@ -33,15 +36,15 @@ func (a *apiError) Code() int32 {
3336
return a.code
3437
}
3538

36-
// MarshalJSON implements the JSON encoding interface
39+
// MarshalJSON implements the JSON encoding interface.
3740
func (a apiError) MarshalJSON() ([]byte, error) {
3841
return json.Marshal(map[string]any{
3942
"code": a.code,
4043
"message": a.message,
4144
})
4245
}
4346

44-
// New creates a new API error with a code and a message
47+
// New creates a new API error with a code and a message.
4548
func New(code int32, message string, args ...any) Error {
4649
if len(args) > 0 {
4750
return &apiError{
@@ -55,20 +58,20 @@ func New(code int32, message string, args ...any) Error {
5558
}
5659
}
5760

58-
// NotFound creates a new not found error
61+
// NotFound creates a new not found error.
5962
func NotFound(message string, args ...any) Error {
6063
if message == "" {
6164
message = "Not found"
6265
}
6366
return New(http.StatusNotFound, message, args...)
6467
}
6568

66-
// NotImplemented creates a new not implemented error
69+
// NotImplemented creates a new not implemented error.
6770
func NotImplemented(message string) Error {
6871
return New(http.StatusNotImplemented, "%s", message)
6972
}
7073

71-
// MethodNotAllowedError represents an error for when the path matches but the method doesn't
74+
// MethodNotAllowedError represents an error for when the path matches but the method doesn't.
7275
type MethodNotAllowedError struct {
7376
code int32
7477
Allowed []string
@@ -79,12 +82,12 @@ func (m *MethodNotAllowedError) Error() string {
7982
return m.message
8083
}
8184

82-
// Code the error code
85+
// Code the error code.
8386
func (m *MethodNotAllowedError) Code() int32 {
8487
return m.code
8588
}
8689

87-
// MarshalJSON implements the JSON encoding interface
90+
// MarshalJSON implements the JSON encoding interface.
8891
func (m MethodNotAllowedError) MarshalJSON() ([]byte, error) {
8992
return json.Marshal(map[string]any{
9093
"code": m.code,
@@ -104,25 +107,33 @@ func errorAsJSON(err Error) []byte {
104107

105108
func flattenComposite(errs *CompositeError) *CompositeError {
106109
var res []error
107-
for _, er := range errs.Errors {
108-
switch e := er.(type) {
109-
case *CompositeError:
110-
if e != nil && len(e.Errors) > 0 {
111-
flat := flattenComposite(e)
112-
if len(flat.Errors) > 0 {
113-
res = append(res, flat.Errors...)
114-
}
115-
}
116-
default:
117-
if e != nil {
118-
res = append(res, e)
119-
}
110+
111+
for _, err := range errs.Errors {
112+
if err == nil {
113+
continue
114+
}
115+
116+
e := &CompositeError{}
117+
if !errors.As(err, &e) {
118+
res = append(res, err)
119+
120+
continue
121+
}
122+
123+
if len(e.Errors) == 0 {
124+
res = append(res, e)
125+
126+
continue
120127
}
128+
129+
flat := flattenComposite(e)
130+
res = append(res, flat.Errors...)
121131
}
132+
122133
return CompositeValidationError(res...)
123134
}
124135

125-
// MethodNotAllowed creates a new method not allowed error
136+
// MethodNotAllowed creates a new method not allowed error.
126137
func MethodNotAllowed(requested string, allow []string) Error {
127138
msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
128139
return &MethodNotAllowedError{
@@ -132,39 +143,55 @@ func MethodNotAllowed(requested string, allow []string) Error {
132143
}
133144
}
134145

135-
// ServeError implements the http error handler interface
146+
// ServeError implements the http error handler interface.
136147
func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
137148
rw.Header().Set("Content-Type", "application/json")
138-
switch e := err.(type) {
139-
case *CompositeError:
140-
er := flattenComposite(e)
149+
150+
if err == nil {
151+
rw.WriteHeader(http.StatusInternalServerError)
152+
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
153+
154+
return
155+
}
156+
157+
errComposite := &CompositeError{}
158+
errMethodNotAllowed := &MethodNotAllowedError{}
159+
var errError Error
160+
161+
switch {
162+
case errors.As(err, &errComposite):
163+
er := flattenComposite(errComposite)
141164
// strips composite errors to first element only
142165
if len(er.Errors) > 0 {
143166
ServeError(rw, r, er.Errors[0])
144-
} else {
145-
// guard against empty CompositeError (invalid construct)
146-
ServeError(rw, r, nil)
167+
168+
return
147169
}
148-
case *MethodNotAllowedError:
149-
rw.Header().Add("Allow", strings.Join(e.Allowed, ","))
150-
rw.WriteHeader(asHTTPCode(int(e.Code())))
170+
171+
// guard against empty CompositeError (invalid construct)
172+
ServeError(rw, r, nil)
173+
174+
case errors.As(err, &errMethodNotAllowed):
175+
rw.Header().Add("Allow", strings.Join(errMethodNotAllowed.Allowed, ","))
176+
rw.WriteHeader(asHTTPCode(int(errMethodNotAllowed.Code())))
151177
if r == nil || r.Method != http.MethodHead {
152-
_, _ = rw.Write(errorAsJSON(e))
178+
_, _ = rw.Write(errorAsJSON(errMethodNotAllowed))
153179
}
154-
case Error:
155-
value := reflect.ValueOf(e)
180+
181+
case errors.As(err, &errError):
182+
value := reflect.ValueOf(errError)
156183
if value.Kind() == reflect.Ptr && value.IsNil() {
157184
rw.WriteHeader(http.StatusInternalServerError)
158185
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
186+
159187
return
160188
}
161-
rw.WriteHeader(asHTTPCode(int(e.Code())))
189+
190+
rw.WriteHeader(asHTTPCode(int(errError.Code())))
162191
if r == nil || r.Method != http.MethodHead {
163-
_, _ = rw.Write(errorAsJSON(e))
192+
_, _ = rw.Write(errorAsJSON(errError))
164193
}
165-
case nil:
166-
rw.WriteHeader(http.StatusInternalServerError)
167-
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
194+
168195
default:
169196
rw.WriteHeader(http.StatusInternalServerError)
170197
if r == nil || r.Method != http.MethodHead {

api_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func TestServeError(t *testing.T) {
9898
)
9999
})
100100

101-
t.Run("with composite erors", func(t *testing.T) {
101+
t.Run("with composite errors", func(t *testing.T) {
102102
t.Run("unrecognized - return internal error with first error only - the second error is ignored", func(t *testing.T) {
103103
compositeErr := &CompositeError{
104104
Errors: []error{
@@ -169,6 +169,28 @@ func TestServeError(t *testing.T) {
169169
)
170170
})
171171

172+
t.Run("check guard against nil members in a CompositeError", func(t *testing.T) {
173+
compositeErr := &CompositeError{
174+
Errors: []error{
175+
New(600, "myApiError"),
176+
nil,
177+
New(601, "myOtherApiError"),
178+
},
179+
}
180+
t.Run("flatten CompositeError should strip nil members", func(t *testing.T) {
181+
flat := flattenComposite(compositeErr)
182+
require.Len(t, flat.Errors, 2)
183+
})
184+
185+
recorder := httptest.NewRecorder()
186+
ServeError(recorder, nil, compositeErr)
187+
assert.Equal(t, CompositeErrorCode, recorder.Code)
188+
assert.JSONEq(t,
189+
`{"code":600,"message":"myApiError"}`,
190+
recorder.Body.String(),
191+
)
192+
})
193+
172194
t.Run("check guard against nil type", func(t *testing.T) {
173195
recorder := httptest.NewRecorder()
174196
ServeError(recorder, nil, nil)

auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package errors
55

66
import "net/http"
77

8-
// Unauthenticated returns an unauthenticated error
8+
// Unauthenticated returns an unauthenticated error.
99
func Unauthenticated(scheme string) Error {
1010
return New(http.StatusUnauthorized, "unauthenticated for %s", scheme)
1111
}

headers.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"net/http"
1010
)
1111

12-
// Validation represents a failure of a precondition
13-
type Validation struct { //nolint: errname
12+
// Validation represents a failure of a precondition.
13+
type Validation struct { //nolint: errname // changing the name to abide by the naming rule would bring a breaking change.
1414
code int32
1515
Name string
1616
In string
@@ -23,12 +23,12 @@ func (e *Validation) Error() string {
2323
return e.message
2424
}
2525

26-
// Code the error code
26+
// Code the error code.
2727
func (e *Validation) Code() int32 {
2828
return e.code
2929
}
3030

31-
// MarshalJSON implements the JSON encoding interface
31+
// MarshalJSON implements the JSON encoding interface.
3232
func (e Validation) MarshalJSON() ([]byte, error) {
3333
return json.Marshal(map[string]any{
3434
"code": e.code,
@@ -40,7 +40,7 @@ func (e Validation) MarshalJSON() ([]byte, error) {
4040
})
4141
}
4242

43-
// ValidateName sets the name for a validation or updates it for a nested property
43+
// ValidateName sets the name for a validation or updates it for a nested property.
4444
func (e *Validation) ValidateName(name string) *Validation {
4545
if name != "" {
4646
if e.Name == "" {
@@ -59,7 +59,7 @@ const (
5959
responseFormatFail = `unsupported media type requested, only %v are available`
6060
)
6161

62-
// InvalidContentType error for an invalid content type
62+
// InvalidContentType error for an invalid content type.
6363
func InvalidContentType(value string, allowed []string) *Validation {
6464
values := make([]any, 0, len(allowed))
6565
for _, v := range allowed {
@@ -75,7 +75,7 @@ func InvalidContentType(value string, allowed []string) *Validation {
7575
}
7676
}
7777

78-
// InvalidResponseFormat error for an unacceptable response format request
78+
// InvalidResponseFormat error for an unacceptable response format request.
7979
func InvalidResponseFormat(value string, allowed []string) *Validation {
8080
values := make([]any, 0, len(allowed))
8181
for _, v := range allowed {

middleware.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
// APIVerificationFailed is an error that contains all the missing info for a mismatched section
13-
// between the api registrations and the api spec
13+
// between the api registrations and the api spec.
1414
type APIVerificationFailed struct { //nolint: errname
1515
Section string `json:"section,omitempty"`
1616
MissingSpecification []string `json:"missingSpecification,omitempty"`

parsing.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"net/http"
1010
)
1111

12-
// ParseError represents a parsing error
12+
// ParseError represents a parsing error.
1313
type ParseError struct {
1414
code int32
1515
Name string
@@ -19,7 +19,7 @@ type ParseError struct {
1919
message string
2020
}
2121

22-
// NewParseError creates a new parse error
22+
// NewParseError creates a new parse error.
2323
func NewParseError(name, in, value string, reason error) *ParseError {
2424
var msg string
2525
if in == "" {
@@ -41,12 +41,12 @@ func (e *ParseError) Error() string {
4141
return e.message
4242
}
4343

44-
// Code returns the http status code for this error
44+
// Code returns the http status code for this error.
4545
func (e *ParseError) Code() int32 {
4646
return e.code
4747
}
4848

49-
// MarshalJSON implements the JSON encoding interface
49+
// MarshalJSON implements the JSON encoding interface.
5050
func (e ParseError) MarshalJSON() ([]byte, error) {
5151
var reason string
5252
if e.Reason != nil {

0 commit comments

Comments
 (0)