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
}

Read our documentation on viewing source code .

Loading