-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
middleware.go
154 lines (135 loc) · 4.77 KB
/
middleware.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
/*
* This file is part of the KubeVirt project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Red Hat, Inc.
*
*/
package middleware
import (
"encoding/json"
"errors"
"fmt"
"runtime/debug"
"github.com/go-kit/kit/endpoint"
"golang.org/x/net/context"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"kubevirt.io/client-go/log"
"kubevirt.io/client-go/precond"
)
type AppError interface {
Cause() error
}
type appError struct {
err error
stack []byte
}
func (e *appError) Error() string {
return e.err.Error()
}
func (e *appError) Cause() error {
return e.err
}
// ValidationError indicates that a DTO validations failed. Can be used by http.EncodeResponseFunc implementations.
type ValidationError struct{ appError }
// MappingError indicates that mapping a DTO to an entity failed. Can be used by endpoint.Endpoint
type MappingError struct{ appError }
// Resource which should be created exists already. Can be used by endpoint.Endpoint or a Service. E.g. lock
type ResourceExistsError struct{ appError }
type ResourceNotFoundError struct{ appError } // Can be thrown before or by a service call
type PreconditionError struct{ appError } // Precondition not met, most likely a bug in a service (service)
type InternalServerError struct{ appError } // Unknown internal error, most likely a bug in a service or a library
type UnsupportedMediaTypeError struct{ appError }
type BadRequestError struct{ appError }
type UnprocessableEntityError struct{ appError }
type KubernetesError struct {
result rest.Result
}
func (k *KubernetesError) Cause() error {
return k.result.Error()
}
func (k *KubernetesError) Error() string {
return k.result.Error().Error()
}
func (k *KubernetesError) Status() (*v1.Status, error) {
b, _ := k.result.Raw()
status := v1.Status{}
err := json.Unmarshal(b, &status)
if err != nil {
return &status, nil
}
return nil, err
}
func (k *KubernetesError) StatusCode() int {
status, err := k.Status()
if err != nil {
return int(status.Code)
} else {
var s int
k.result.StatusCode(&s)
return s
}
}
func (k *KubernetesError) Body() []byte {
body, _ := k.result.Raw()
return body
}
// InternalErrorMiddleware is a convenience middleware which can be used in combination with panics.
// After data is sanitized and validated, services can expect to get reasonable valid data passed (e.g.
// object not nil, string not empty, ...). With this middleware in place the service can throw an exception with a
// precond.PreconditionError as payload. This middleware will catch that and translate it into an application
// level PreconditionError. All other detected panics will be converted into an InternalServerError. In both cases it
// is most likely that there is an error within the application or a library. Long story short, this is about
// failing early in non recoverable situations.
func InternalErrorMiddleware() endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (d interface{}, e error) {
var data interface{}
var err error
defer func() {
if r := recover(); r != nil {
switch t := r.(type) {
case *precond.PreconditionError:
// Shortcut for failing precondition checks
d, e = &PreconditionError{appError{err: t, stack: debug.Stack()}}, nil
default:
// Other panics should never happen, so map them to InternalServerError
d = &InternalServerError{
appError{
err: errors.New(fmt.Sprint(t)),
stack: debug.Stack(),
}}
e = nil
}
// TODO log it with a logger at the right locations
log.Log.Criticalf("stacktrace: %v", string(debug.Stack()))
}
}()
data, err = next(ctx, request)
// From here on all AppErrors returned through the err return value are treated as app
// payload and returned with the right http return code
// For instance a service can just return an AppError instance as normal err and this check
// makes sure that our application error handler handles the response
if _, ok := err.(AppError); ok {
log.Log.Criticalf("%s", err)
return err, nil
}
return data, err
}
}
}
func NewResourceConflictError(msg string) *ResourceNotFoundError {
return &ResourceNotFoundError{appError{err: fmt.Errorf(msg)}}
}