blob: d1a752c42b97a9269a115a01e8b28e9172399836 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package errors
16
17import (
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "reflect"
22 "strings"
23)
24
25// DefaultHTTPCode is used when the error Code cannot be used as an HTTP code.
26var DefaultHTTPCode = 422
27
28// Error represents a error interface all swagger framework errors implement
29type Error interface {
30 error
31 Code() int32
32}
33
34type apiError struct {
35 code int32
36 message string
37}
38
39func (a *apiError) Error() string {
40 return a.message
41}
42
43func (a *apiError) Code() int32 {
44 return a.code
45}
46
47// New creates a new API error with a code and a message
48func New(code int32, message string, args ...interface{}) Error {
49 if len(args) > 0 {
50 return &apiError{code, fmt.Sprintf(message, args...)}
51 }
52 return &apiError{code, message}
53}
54
55// NotFound creates a new not found error
56func NotFound(message string, args ...interface{}) Error {
57 if message == "" {
58 message = "Not found"
59 }
60 return New(http.StatusNotFound, fmt.Sprintf(message, args...))
61}
62
63// NotImplemented creates a new not implemented error
64func NotImplemented(message string) Error {
65 return New(http.StatusNotImplemented, message)
66}
67
68// MethodNotAllowedError represents an error for when the path matches but the method doesn't
69type MethodNotAllowedError struct {
70 code int32
71 Allowed []string
72 message string
73}
74
75func (m *MethodNotAllowedError) Error() string {
76 return m.message
77}
78
79// Code the error code
80func (m *MethodNotAllowedError) Code() int32 {
81 return m.code
82}
83
84func errorAsJSON(err Error) []byte {
85 b, _ := json.Marshal(struct {
86 Code int32 `json:"code"`
87 Message string `json:"message"`
88 }{err.Code(), err.Error()})
89 return b
90}
91
92func flattenComposite(errs *CompositeError) *CompositeError {
93 var res []error
94 for _, er := range errs.Errors {
95 switch e := er.(type) {
96 case *CompositeError:
97 if len(e.Errors) > 0 {
98 flat := flattenComposite(e)
99 if len(flat.Errors) > 0 {
100 res = append(res, flat.Errors...)
101 }
102 }
103 default:
104 if e != nil {
105 res = append(res, e)
106 }
107 }
108 }
109 return CompositeValidationError(res...)
110}
111
112// MethodNotAllowed creates a new method not allowed error
113func MethodNotAllowed(requested string, allow []string) Error {
114 msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
115 return &MethodNotAllowedError{code: http.StatusMethodNotAllowed, Allowed: allow, message: msg}
116}
117
118const head = "HEAD"
119
120// ServeError the error handler interface implementation
121func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
122 rw.Header().Set("Content-Type", "application/json")
123 switch e := err.(type) {
124 case *CompositeError:
125 er := flattenComposite(e)
126 // strips composite errors to first element only
127 if len(er.Errors) > 0 {
128 ServeError(rw, r, er.Errors[0])
129 } else {
130 // guard against empty CompositeError (invalid construct)
131 ServeError(rw, r, nil)
132 }
133 case *MethodNotAllowedError:
134 rw.Header().Add("Allow", strings.Join(err.(*MethodNotAllowedError).Allowed, ","))
135 rw.WriteHeader(asHTTPCode(int(e.Code())))
136 if r == nil || r.Method != head {
137 rw.Write(errorAsJSON(e))
138 }
139 case Error:
140 value := reflect.ValueOf(e)
141 if value.Kind() == reflect.Ptr && value.IsNil() {
142 rw.WriteHeader(http.StatusInternalServerError)
143 rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
144 return
145 }
146 rw.WriteHeader(asHTTPCode(int(e.Code())))
147 if r == nil || r.Method != head {
148 rw.Write(errorAsJSON(e))
149 }
150 case nil:
151 rw.WriteHeader(http.StatusInternalServerError)
152 rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
153 default:
154 rw.WriteHeader(http.StatusInternalServerError)
155 if r == nil || r.Method != head {
156 rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
157 }
158 }
159}
160
161func asHTTPCode(input int) int {
162 if input >= 600 {
163 return DefaultHTTPCode
164 }
165 return input
166}