Skip to content

Commit 9854dc7

Browse files
committed
Add more grpc error types
Add all grpc error types and error matching used by Moby. Signed-off-by: Derek McGowan <[email protected]>
1 parent 3abe029 commit 9854dc7

4 files changed

Lines changed: 401 additions & 39 deletions

File tree

errors.go

Lines changed: 275 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,64 +29,314 @@ package errdefs
2929
import (
3030
"context"
3131
"errors"
32+
"fmt"
3233
)
3334

3435
// Definitions of common error types used throughout containerd. All containerd
3536
// errors returned by most packages will map into one of these errors classes.
3637
// Packages should return errors of these types when they want to instruct a
3738
// client to take a particular action.
3839
//
39-
// For the most part, we just try to provide local grpc errors. Most conditions
40-
// map very well to those defined by grpc.
40+
// These errors map closely to grpc errors.
4141
var (
42-
ErrUnknown = errors.New("unknown") // used internally to represent a missed mapping.
43-
ErrInvalidArgument = errors.New("invalid argument")
44-
ErrNotFound = errors.New("not found")
45-
ErrAlreadyExists = errors.New("already exists")
46-
ErrFailedPrecondition = errors.New("failed precondition")
47-
ErrUnavailable = errors.New("unavailable")
48-
ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented
42+
ErrUnknown = errUnknown{}
43+
ErrInvalidArgument = errInvalidArgument{}
44+
ErrNotFound = errNotFound{}
45+
ErrAlreadyExists = errAlreadyExists{}
46+
ErrPermissionDenied = errPermissionDenied{}
47+
ErrResourceExhausted = errResourceExhausted{}
48+
ErrFailedPrecondition = errFailedPrecondition{}
49+
ErrConflict = errConflict{}
50+
ErrNotModified = errNotModified{}
51+
ErrAborted = errAborted{}
52+
ErrOutOfRange = errOutOfRange{}
53+
ErrNotImplemented = errNotImplemented{}
54+
ErrInternal = errInternal{}
55+
ErrUnavailable = errUnavailable{}
56+
ErrDataLoss = errDataLoss{}
57+
ErrUnauthenticated = errUnauthorized{}
4958
)
5059

60+
// cancelled maps to Moby's "ErrCancelled"
61+
type cancelled interface {
62+
ErrCancelled()
63+
}
64+
65+
// IsCanceled returns true if the error is due to `context.Canceled`.
66+
func IsCanceled(err error) bool {
67+
return errors.Is(err, context.Canceled) || isInterface[cancelled](err)
68+
}
69+
70+
type errUnknown struct{}
71+
72+
func (errUnknown) Error() string { return "unknown" }
73+
74+
func (errUnknown) Unknown() {}
75+
76+
type errUnexpectedStatus struct {
77+
status int
78+
}
79+
80+
const unexpectedStatusPrefix = "unexpected status "
81+
82+
func (e errUnexpectedStatus) Error() string {
83+
return fmt.Sprintf("%s%d", unexpectedStatusPrefix, e.status)
84+
}
85+
86+
func (errUnexpectedStatus) Unknown() {}
87+
88+
// unknown maps to Moby's "ErrUnknown"
89+
type unknown interface {
90+
Unknown()
91+
}
92+
93+
// IsUnknown returns true if the error is due to an unknown error,
94+
// unhandled condition or unexpected response.
95+
func IsUnknown(err error) bool {
96+
return errors.Is(err, errUnknown{}) || isInterface[unknown](err)
97+
}
98+
99+
type errInvalidArgument struct{}
100+
101+
func (errInvalidArgument) Error() string { return "invalid argument" }
102+
103+
func (errInvalidArgument) InvalidParameter() {}
104+
105+
// invalidParameter maps to Moby's "ErrInvalidParameter"
106+
type invalidParameter interface {
107+
InvalidParameter()
108+
}
109+
51110
// IsInvalidArgument returns true if the error is due to an invalid argument
52111
func IsInvalidArgument(err error) bool {
53-
return errors.Is(err, ErrInvalidArgument)
112+
return errors.Is(err, ErrInvalidArgument) || isInterface[invalidParameter](err)
113+
}
114+
115+
// deadlineExceed maps to Moby's "ErrDeadline"
116+
type deadlineExceeded interface {
117+
DeadlineExceeded()
118+
}
119+
120+
// IsDeadlineExceeded returns true if the error is due to
121+
// `context.DeadlineExceeded`.
122+
func IsDeadlineExceeded(err error) bool {
123+
return errors.Is(err, context.DeadlineExceeded) || isInterface[deadlineExceeded](err)
124+
}
125+
126+
type errNotFound struct{}
127+
128+
func (errNotFound) Error() string { return "not found" }
129+
130+
func (errNotFound) NotFound() {}
131+
132+
// notFound maps to Moby's "ErrNotFound"
133+
type notFound interface {
134+
NotFound()
54135
}
55136

56137
// IsNotFound returns true if the error is due to a missing object
57138
func IsNotFound(err error) bool {
58-
return errors.Is(err, ErrNotFound)
139+
return errors.Is(err, ErrNotFound) || isInterface[notFound](err)
59140
}
60141

142+
type errAlreadyExists struct{}
143+
144+
func (errAlreadyExists) Error() string { return "already exists" }
145+
61146
// IsAlreadyExists returns true if the error is due to an already existing
62147
// metadata item
63148
func IsAlreadyExists(err error) bool {
64149
return errors.Is(err, ErrAlreadyExists)
65150
}
66151

67-
// IsFailedPrecondition returns true if an operation could not proceed to the
68-
// lack of a particular condition
152+
type errPermissionDenied struct{}
153+
154+
func (errPermissionDenied) Error() string { return "permission denied" }
155+
156+
// forbidden maps to Moby's "ErrForbidden"
157+
type forbidden interface {
158+
Forbidden()
159+
}
160+
161+
// IsPermissionDenied returns true if the error is due to permission denied
162+
// or forbidden (403) response
163+
func IsPermissionDenied(err error) bool {
164+
return errors.Is(err, ErrPermissionDenied) || isInterface[forbidden](err)
165+
}
166+
167+
type errResourceExhausted struct{}
168+
169+
func (errResourceExhausted) Error() string { return "resource exhausted" }
170+
171+
// IsResourceExhausted returns true if the error is due to
172+
// a lack of resources or too many attempts.
173+
func IsResourceExhausted(err error) bool {
174+
return errors.Is(err, errResourceExhausted{})
175+
}
176+
177+
type errFailedPrecondition struct{}
178+
179+
func (e errFailedPrecondition) Error() string { return "failed precondition" }
180+
181+
// IsFailedPrecondition returns true if an operation could not proceed due to
182+
// the lack of a particular condition
69183
func IsFailedPrecondition(err error) bool {
70-
return errors.Is(err, ErrFailedPrecondition)
184+
return errors.Is(err, errFailedPrecondition{})
71185
}
72186

73-
// IsUnavailable returns true if the error is due to a resource being unavailable
74-
func IsUnavailable(err error) bool {
75-
return errors.Is(err, ErrUnavailable)
187+
type errConflict struct{}
188+
189+
func (errConflict) Error() string { return "conflict" }
190+
191+
func (errConflict) Conflict() {}
192+
193+
// conflict maps to Moby's "ErrConflict"
194+
type conflict interface {
195+
Conflict()
196+
}
197+
198+
// IsConflict returns true if an operation could not proceed due to
199+
// a conflict.
200+
func IsConflict(err error) bool {
201+
return errors.Is(err, errConflict{}) || isInterface[conflict](err)
202+
}
203+
204+
type errNotModified struct{}
205+
206+
func (errNotModified) Error() string { return "not modified" }
207+
208+
func (errNotModified) NotModified() {}
209+
210+
// notModified maps to Moby's "ErrNotModified"
211+
type notModified interface {
212+
NotModified()
213+
}
214+
215+
// IsNotModified returns true if an operation could not proceed due
216+
// to an object not modified from a previous state.
217+
func IsNotModified(err error) bool {
218+
return errors.Is(err, errNotModified{}) || isInterface[notModified](err)
219+
}
220+
221+
type errAborted struct{}
222+
223+
func (errAborted) Error() string { return "aborted" }
224+
225+
// IsAborted returns true if an operation was aborted.
226+
func IsAborted(err error) bool {
227+
return errors.Is(err, errAborted{})
228+
}
229+
230+
type errOutOfRange struct{}
231+
232+
func (errOutOfRange) Error() string { return "out of range" }
233+
234+
// IsOutOfRange returns true if an operation could not proceed due
235+
// to data being out of the expected range.
236+
func IsOutOfRange(err error) bool {
237+
return errors.Is(err, errOutOfRange{})
238+
}
239+
240+
type errNotImplemented struct{}
241+
242+
func (errNotImplemented) Error() string { return "not implemented" }
243+
244+
func (errNotImplemented) NotImplemented() {}
245+
246+
// notImplemented maps to Moby's "ErrNotImplemented"
247+
type notImplemented interface {
248+
NotImplemented()
76249
}
77250

78251
// IsNotImplemented returns true if the error is due to not being implemented
79252
func IsNotImplemented(err error) bool {
80-
return errors.Is(err, ErrNotImplemented)
253+
return errors.Is(err, errNotImplemented{}) || isInterface[notImplemented](err)
81254
}
82255

83-
// IsCanceled returns true if the error is due to `context.Canceled`.
84-
func IsCanceled(err error) bool {
85-
return errors.Is(err, context.Canceled)
256+
type errInternal struct{}
257+
258+
func (errInternal) Error() string { return "internal" }
259+
260+
func (errInternal) System() {}
261+
262+
// system maps to Moby's "ErrSystem"
263+
type system interface {
264+
System()
86265
}
87266

88-
// IsDeadlineExceeded returns true if the error is due to
89-
// `context.DeadlineExceeded`.
90-
func IsDeadlineExceeded(err error) bool {
91-
return errors.Is(err, context.DeadlineExceeded)
267+
// IsInternal returns true if the error returns to an internal or system error
268+
func IsInternal(err error) bool {
269+
return errors.Is(err, errInternal{}) || isInterface[system](err)
270+
}
271+
272+
type errUnavailable struct{}
273+
274+
func (errUnavailable) Error() string { return "unavailable" }
275+
276+
func (errUnavailable) Unavailable() {}
277+
278+
// unavailable maps to Moby's "ErrUnavailable"
279+
type unavailable interface {
280+
Unavailable()
281+
}
282+
283+
// IsUnavailable returns true if the error is due to a resource being unavailable
284+
func IsUnavailable(err error) bool {
285+
return errors.Is(err, errUnavailable{}) || isInterface[unavailable](err)
286+
}
287+
288+
type errDataLoss struct{}
289+
290+
func (errDataLoss) Error() string { return "data loss" }
291+
292+
func (errDataLoss) DataLoss() {}
293+
294+
// dataLoss maps to Moby's "ErrDataLoss"
295+
type dataLoss interface {
296+
DataLoss()
297+
}
298+
299+
// IsDataLoss returns true if data during an operation was lost or corrupted
300+
func IsDataLoss(err error) bool {
301+
return errors.Is(err, errDataLoss{}) || isInterface[dataLoss](err)
302+
}
303+
304+
type errUnauthorized struct{}
305+
306+
func (errUnauthorized) Error() string { return "unauthorized" }
307+
308+
func (errUnauthorized) Unauthorized() {}
309+
310+
// unauthorized maps to Moby's "ErrUnauthorized"
311+
type unauthorized interface {
312+
Unauthorized()
313+
}
314+
315+
// IsUnauthorized returns true if the error indicates that the user was
316+
// unauthenticated or unauthorized.
317+
func IsUnauthorized(err error) bool {
318+
return errors.Is(err, errUnauthorized{}) || isInterface[unauthorized](err)
319+
}
320+
321+
func isInterface[T any](err error) bool {
322+
for {
323+
switch x := err.(type) {
324+
case T:
325+
return true
326+
case interface{ Unwrap() error }:
327+
err = x.Unwrap()
328+
if err == nil {
329+
return false
330+
}
331+
case interface{ Unwrap() []error }:
332+
for _, err := range x.Unwrap() {
333+
if isInterface[T](err) {
334+
return true
335+
}
336+
}
337+
return false
338+
default:
339+
return false
340+
}
341+
}
92342
}

errors_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package errdefs
18+
19+
import (
20+
"context"
21+
"errors"
22+
"testing"
23+
)
24+
25+
func TestInvalidArgument(t *testing.T) {
26+
for _, match := range []error{
27+
ErrInvalidArgument,
28+
&errInvalidArgument{},
29+
&customInvalidArgument{},
30+
&wrappedInvalidArgument{errors.New("invalid parameter")},
31+
} {
32+
if !IsInvalidArgument(match) {
33+
t.Errorf("error did not match invalid argument: %#v", match)
34+
}
35+
}
36+
for _, nonMatch := range []error{
37+
ErrUnknown,
38+
context.Canceled,
39+
errors.New("invalid argument"),
40+
} {
41+
if IsInvalidArgument(nonMatch) {
42+
t.Errorf("error unexpectedly matched invalid argument: %#v", nonMatch)
43+
}
44+
}
45+
}
46+
47+
type customInvalidArgument struct{}
48+
49+
func (*customInvalidArgument) Error() string {
50+
return "my own invalid argument"
51+
}
52+
53+
func (*customInvalidArgument) InvalidParameter() {}
54+
55+
type wrappedInvalidArgument struct{ error }
56+
57+
func (*wrappedInvalidArgument) InvalidParameter() {}

0 commit comments

Comments
 (0)