Welcome to mirror list, hosted at ThFree Co, Russian Federation.

error.go « helper « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 7a6d16f3f5674c1d21cadb48e85ba36bb57a0370 (plain)
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package helper

import (
	"errors"
	"fmt"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"
)

type statusWrapper struct {
	error
	status *status.Status
}

func (sw statusWrapper) GRPCStatus() *status.Status {
	return sw.status
}

func (sw statusWrapper) Unwrap() error {
	return sw.error
}

// ErrCanceled wraps err with codes.Canceled, unless err is already a gRPC error.
func ErrCanceled(err error) error { return wrapError(codes.Canceled, err) }

// ErrDeadlineExceeded wraps err with codes.DeadlineExceeded, unless err is already a gRPC error.
func ErrDeadlineExceeded(err error) error { return wrapError(codes.DeadlineExceeded, err) }

// ErrInternal wraps err with codes.Internal, unless err is already a gRPC error.
func ErrInternal(err error) error { return wrapError(codes.Internal, err) }

// ErrInvalidArgument wraps err with codes.InvalidArgument, unless err is already a gRPC error.
func ErrInvalidArgument(err error) error { return wrapError(codes.InvalidArgument, err) }

// ErrNotFound wraps error with codes.NotFound, unless err is already a gRPC error.
func ErrNotFound(err error) error { return wrapError(codes.NotFound, err) }

// ErrFailedPrecondition wraps err with codes.FailedPrecondition, unless err is already a gRPC
// error.
func ErrFailedPrecondition(err error) error { return wrapError(codes.FailedPrecondition, err) }

// ErrUnavailable wraps err with codes.Unavailable, unless err is already a gRPC error.
func ErrUnavailable(err error) error { return wrapError(codes.Unavailable, err) }

// ErrPermissionDenied wraps err with codes.PermissionDenied, unless err is already a gRPC error.
func ErrPermissionDenied(err error) error { return wrapError(codes.PermissionDenied, err) }

// ErrAlreadyExists wraps err with codes.AlreadyExists, unless err is already a gRPC error.
func ErrAlreadyExists(err error) error { return wrapError(codes.AlreadyExists, err) }

// ErrAborted wraps err with codes.Aborted, unless err is already a gRPC error.
func ErrAborted(err error) error { return wrapError(codes.Aborted, err) }

// wrapError wraps the given error with the error code unless it's already a gRPC error. If given
// nil it will return nil.
func wrapError(code codes.Code, err error) error {
	if err == nil {
		return nil
	}

	_, ok := status.FromError(err)
	if ok {
		return err
	}

	if foundCode := GrpcCode(err); foundCode != codes.OK && foundCode != codes.Unknown {
		code = foundCode
	}

	return statusWrapper{error: err, status: status.New(code, err.Error())}
}

// ErrCanceledf wraps a formatted error with codes.Canceled, unless the formatted error is a
// wrapped gRPC error.
func ErrCanceledf(format string, a ...interface{}) error {
	return formatError(codes.Canceled, format, a...)
}

// ErrDeadlineExceededf wraps a formatted error with codes.DeadlineExceeded, unless the formatted
// error is a wrapped gRPC error.
func ErrDeadlineExceededf(format string, a ...interface{}) error {
	return formatError(codes.DeadlineExceeded, format, a...)
}

// ErrInternalf wraps a formatted error with codes.Internal, unless the formatted error is a
// wrapped gRPC error.
func ErrInternalf(format string, a ...interface{}) error {
	return formatError(codes.Internal, format, a...)
}

// ErrInvalidArgumentf wraps a formatted error with codes.InvalidArgument, unless the formatted
// error is a wrapped gRPC error.
func ErrInvalidArgumentf(format string, a ...interface{}) error {
	return formatError(codes.InvalidArgument, format, a...)
}

// ErrNotFoundf wraps a formatted error with codes.NotFound, unless the
// formatted error is a wrapped gRPC error.
func ErrNotFoundf(format string, a ...interface{}) error {
	return formatError(codes.NotFound, format, a...)
}

// ErrFailedPreconditionf wraps a formatted error with codes.FailedPrecondition, unless the
// formatted error is a wrapped gRPC error.
func ErrFailedPreconditionf(format string, a ...interface{}) error {
	return formatError(codes.FailedPrecondition, format, a...)
}

// ErrUnavailablef wraps a formatted error with codes.Unavailable, unless the
// formatted error is a wrapped gRPC error.
func ErrUnavailablef(format string, a ...interface{}) error {
	return formatError(codes.Unavailable, format, a...)
}

// ErrPermissionDeniedf wraps a formatted error with codes.PermissionDenied, unless the formatted
// error is a wrapped gRPC error.
func ErrPermissionDeniedf(format string, a ...interface{}) error {
	return formatError(codes.PermissionDenied, format, a...)
}

// ErrAlreadyExistsf wraps a formatted error with codes.AlreadyExists, unless the formatted error is
// a wrapped gRPC error.
func ErrAlreadyExistsf(format string, a ...interface{}) error {
	return formatError(codes.AlreadyExists, format, a...)
}

// ErrAbortedf wraps a formatted error with codes.Aborted, unless the formatted error is a wrapped
// gRPC error.
func ErrAbortedf(format string, a ...interface{}) error {
	return formatError(codes.Aborted, format, a...)
}

// grpcErrorMessageWrapper is used to wrap a gRPC `status.Status`-style error such that it behaves
// like a `status.Status`, except that it generates a readable error message.
type grpcErrorMessageWrapper struct {
	*status.Status
}

func (e grpcErrorMessageWrapper) GRPCStatus() *status.Status {
	return e.Status
}

func (e grpcErrorMessageWrapper) Error() string {
	return e.Message()
}

func (e grpcErrorMessageWrapper) Unwrap() error {
	return e.Status.Err()
}

// formatError will create a new error from the given format string. If the error string contains a
// %w verb and its corresponding error has a gRPC error code, then the returned error will keep this
// gRPC error code instead of using the one provided as an argument.
func formatError(code codes.Code, format string, a ...interface{}) error {
	args := make([]interface{}, 0, len(a))
	for _, a := range a {
		err, ok := a.(error)
		if !ok {
			args = append(args, a)
			continue
		}

		status, ok := status.FromError(err)
		if !ok {
			args = append(args, a)
			continue
		}

		// Wrap gRPC status errors so that the resulting error message stays readable.
		args = append(args, grpcErrorMessageWrapper{status})
	}

	err := fmt.Errorf(format, args...)

	for current := err; current != nil; current = errors.Unwrap(current) {
		nestedCode := GrpcCode(current)
		if nestedCode != codes.OK && nestedCode != codes.Unknown {
			code = nestedCode
		}
	}

	return statusWrapper{err, status.New(code, err.Error())}
}

// ErrWithDetails adds the given details to the error if it is a gRPC status whose code is not OK.
func ErrWithDetails(err error, details ...proto.Message) (error, error) {
	if GrpcCode(err) == codes.OK {
		return nil, fmt.Errorf("no error given")
	}

	st, ok := status.FromError(err)
	if !ok {
		return nil, fmt.Errorf("error is not a gRPC status")
	}

	proto := st.Proto()
	for _, detail := range details {
		marshaled, err := anypb.New(detail)
		if err != nil {
			return nil, err
		}

		proto.Details = append(proto.Details, marshaled)
	}

	return statusWrapper{err, status.FromProto(proto)}, nil
}

// GrpcCode translates errors into codes.Code values.
// It unwraps the nested errors until it finds the most nested one that returns the codes.Code.
// If err is nil it returns codes.OK.
// If no codes.Code found it returns codes.Unknown.
func GrpcCode(err error) codes.Code {
	if err == nil {
		return codes.OK
	}

	code := codes.Unknown
	for ; err != nil; err = errors.Unwrap(err) {
		st, ok := status.FromError(err)
		if ok {
			code = st.Code()
		}
	}

	return code
}