1
|
|
// Copyright 2019 Aporeto Inc.
|
2
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
3
|
|
// you may not use this file except in compliance with the License.
|
4
|
|
// You may obtain a copy of the License at
|
5
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
6
|
|
// Unless required by applicable law or agreed to in writing, software
|
7
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
8
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
9
|
|
// See the License for the specific language governing permissions and
|
10
|
|
// limitations under the License.
|
11
|
|
|
12
|
|
package elemental
|
13
|
|
|
14
|
|
import (
|
15
|
|
"encoding/json"
|
16
|
|
"fmt"
|
17
|
|
"net/http"
|
18
|
|
"strings"
|
19
|
|
)
|
20
|
|
|
21
|
|
// IsErrorWithCode returns true if the given error is an elemental.Error
|
22
|
|
// or elemental.Errors with the status set to the given code.
|
23
|
|
func IsErrorWithCode(err error, code int) bool {
|
24
|
|
|
25
|
14
|
var c int
|
26
|
14
|
switch e := err.(type) {
|
27
|
14
|
case Error:
|
28
|
14
|
c = e.Code
|
29
|
14
|
case Errors:
|
30
|
14
|
c = e.Code()
|
31
|
|
}
|
32
|
|
|
33
|
14
|
return c == code
|
34
|
|
}
|
35
|
|
|
36
|
|
// An Error represents a computational error.
|
37
|
|
//
|
38
|
|
// They can be encoded and sent back to the clients.
|
39
|
|
type Error struct {
|
40
|
|
Code int `msgpack:"code" json:"code,omitempty"`
|
41
|
|
Description string `msgpack:"description" json:"description"`
|
42
|
|
Subject string `msgpack:"subject" json:"subject"`
|
43
|
|
Title string `msgpack:"title" json:"title"`
|
44
|
|
Data interface{} `msgpack:"data" json:"data,omitempty"`
|
45
|
|
Trace string `msgpack:"trace" json:"trace,omitempty"`
|
46
|
|
}
|
47
|
|
|
48
|
|
// NewError returns a new Error.
|
49
|
|
func NewError(title, description, subject string, code int) Error {
|
50
|
|
|
51
|
14
|
return NewErrorWithData(title, description, subject, code, nil)
|
52
|
|
}
|
53
|
|
|
54
|
|
// NewErrorWithData returns a new Error with the given opaque data.
|
55
|
|
func NewErrorWithData(title, description, subject string, code int, data interface{}) Error {
|
56
|
|
|
57
|
14
|
return Error{
|
58
|
14
|
Code: code,
|
59
|
14
|
Description: description,
|
60
|
14
|
Subject: subject,
|
61
|
14
|
Title: title,
|
62
|
14
|
Data: data,
|
63
|
|
}
|
64
|
|
}
|
65
|
|
|
66
|
|
func (e Error) Error() string {
|
67
|
|
|
68
|
14
|
if e.Trace != "" {
|
69
|
14
|
return fmt.Sprintf("error %d (%s): %s: %s [trace: %s]", e.Code, e.Subject, e.Title, e.Description, e.Trace)
|
70
|
|
}
|
71
|
|
|
72
|
14
|
return fmt.Sprintf("error %d (%s): %s: %s", e.Code, e.Subject, e.Title, e.Description)
|
73
|
|
}
|
74
|
|
|
75
|
|
// Errors represents a list of Error.
|
76
|
|
type Errors []Error
|
77
|
|
|
78
|
|
// NewErrors creates a new Errors.
|
79
|
|
func NewErrors(errors ...error) Errors {
|
80
|
|
|
81
|
14
|
out := Errors{}
|
82
|
14
|
if len(errors) == 0 {
|
83
|
14
|
return out
|
84
|
|
}
|
85
|
|
|
86
|
14
|
return out.Append(errors...)
|
87
|
|
}
|
88
|
|
|
89
|
|
func (e Errors) Error() string {
|
90
|
|
|
91
|
14
|
strs := make([]string, len(e))
|
92
|
|
|
93
|
|
for i := range e {
|
94
|
14
|
strs[i] = e[i].Error()
|
95
|
|
}
|
96
|
|
|
97
|
14
|
return strings.Join(strs, ", ")
|
98
|
|
}
|
99
|
|
|
100
|
|
// Code returns the code of the first error code in the Errors.
|
101
|
|
func (e Errors) Code() int {
|
102
|
|
|
103
|
14
|
if len(e) == 0 {
|
104
|
14
|
return -1
|
105
|
|
}
|
106
|
|
|
107
|
14
|
return e[0].Code
|
108
|
|
}
|
109
|
|
|
110
|
|
// Append returns returns a copy of the receiver containing
|
111
|
|
// also the given errors.
|
112
|
|
func (e Errors) Append(errs ...error) Errors {
|
113
|
|
|
114
|
14
|
out := append(Errors{}, e...)
|
115
|
|
|
116
|
|
for _, err := range errs {
|
117
|
14
|
switch er := err.(type) {
|
118
|
14
|
case Error:
|
119
|
14
|
out = append(out, er)
|
120
|
14
|
case Errors:
|
121
|
14
|
out = append(out, er...)
|
122
|
14
|
default:
|
123
|
14
|
out = append(out, NewError("Internal Server Error", err.Error(), "elemental", http.StatusInternalServerError))
|
124
|
|
}
|
125
|
|
}
|
126
|
|
|
127
|
14
|
return out
|
128
|
|
}
|
129
|
|
|
130
|
|
// Trace returns Errors with all inside Error marked with the
|
131
|
|
// given trace ID.
|
132
|
|
func (e Errors) Trace(id string) Errors {
|
133
|
|
|
134
|
14
|
out := Errors{}
|
135
|
|
|
136
|
|
for _, err := range e {
|
137
|
14
|
err.Trace = id
|
138
|
14
|
out = append(out, err)
|
139
|
|
}
|
140
|
|
|
141
|
14
|
return out
|
142
|
|
}
|
143
|
|
|
144
|
|
// DecodeErrors decodes the given bytes into a en elemental.Errors.
|
145
|
|
func DecodeErrors(data []byte) (Errors, error) {
|
146
|
|
|
147
|
14
|
es := []Error{}
|
148
|
14
|
if err := json.Unmarshal(data, &es); err != nil {
|
149
|
14
|
return nil, err
|
150
|
|
}
|
151
|
|
|
152
|
14
|
e := NewErrors()
|
153
|
|
for _, err := range es {
|
154
|
14
|
e = append(e, err)
|
155
|
|
}
|
156
|
|
|
157
|
14
|
return e, nil
|
158
|
|
}
|
159
|
|
|
160
|
|
// IsValidationError returns true if the given error is a validation error
|
161
|
|
// with the given title for the given attribute.
|
162
|
|
func IsValidationError(err error, title string, attribute string) bool {
|
163
|
|
|
164
|
14
|
var elementalError Error
|
165
|
14
|
switch e := err.(type) {
|
166
|
|
|
167
|
14
|
case Errors:
|
168
|
14
|
if e.Code() != http.StatusUnprocessableEntity {
|
169
|
14
|
return false
|
170
|
|
}
|
171
|
14
|
if len(e) != 1 {
|
172
|
14
|
return false
|
173
|
|
}
|
174
|
14
|
elementalError = e[0]
|
175
|
|
|
176
|
14
|
case Error:
|
177
|
14
|
if e.Code != http.StatusUnprocessableEntity {
|
178
|
14
|
return false
|
179
|
|
}
|
180
|
14
|
elementalError = e
|
181
|
|
|
182
|
14
|
default:
|
183
|
14
|
return false
|
184
|
|
}
|
185
|
|
|
186
|
14
|
if elementalError.Title != title {
|
187
|
14
|
return false
|
188
|
|
}
|
189
|
|
|
190
|
14
|
if elementalError.Data == nil {
|
191
|
14
|
return false
|
192
|
|
}
|
193
|
|
|
194
|
14
|
m, ok := elementalError.Data.(map[string]interface{})
|
195
|
14
|
if !ok {
|
196
|
14
|
return false
|
197
|
|
}
|
198
|
|
|
199
|
14
|
return m["attribute"].(string) == attribute
|
200
|
|
}
|