/* Copyright The containerd Authors. 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. */ package errdefs import "context" // Resolve returns the first error found in the error chain which matches an // error defined in this package or context error. A raw, unwrapped error is // returned or ErrUnknown if no matching error is found. // // This is useful for determining a response code based on the outermost wrapped // error rather than the original cause. For example, a not found error deep // in the code may be wrapped as an invalid argument. When determining status // code from Is* functions, the depth or ordering of the error is not // considered. // // The search order is depth first, a wrapped error returned from any part of // the chain from `Unwrap() error` will be returned before any joined errors // as returned by `Unwrap() []error`. func Resolve(err error) error { if err == nil { return nil } err = firstError(err) if err == nil { err = ErrUnknown } return err } func firstError(err error) error { for { switch err { case ErrUnknown, ErrInvalidArgument, ErrNotFound, ErrAlreadyExists, ErrPermissionDenied, ErrResourceExhausted, ErrFailedPrecondition, ErrConflict, ErrNotModified, ErrAborted, ErrOutOfRange, ErrNotImplemented, ErrInternal, ErrUnavailable, ErrDataLoss, ErrUnauthenticated, context.DeadlineExceeded, context.Canceled: return err } switch e := err.(type) { case customMessage: err = e.err case unknown: return ErrUnknown case invalidParameter: return ErrInvalidArgument case notFound: return ErrNotFound case alreadyExists: return ErrAlreadyExists case forbidden: return ErrPermissionDenied case resourceExhausted: return ErrResourceExhausted case failedPrecondition: return ErrFailedPrecondition case conflict: return ErrConflict case notModified: return ErrNotModified case aborted: return ErrAborted case errOutOfRange: return ErrOutOfRange case notImplemented: return ErrNotImplemented case system: return ErrInternal case unavailable: return ErrUnavailable case dataLoss: return ErrDataLoss case unauthorized: return ErrUnauthenticated case deadlineExceeded: return context.DeadlineExceeded case cancelled: return context.Canceled case interface{ Unwrap() error }: err = e.Unwrap() if err == nil { return nil } case interface{ Unwrap() []error }: for _, ue := range e.Unwrap() { if fe := firstError(ue); fe != nil { return fe } } return nil case interface{ Is(error) bool }: for _, target := range []error{ErrUnknown, ErrInvalidArgument, ErrNotFound, ErrAlreadyExists, ErrPermissionDenied, ErrResourceExhausted, ErrFailedPrecondition, ErrConflict, ErrNotModified, ErrAborted, ErrOutOfRange, ErrNotImplemented, ErrInternal, ErrUnavailable, ErrDataLoss, ErrUnauthenticated, context.DeadlineExceeded, context.Canceled} { if e.Is(target) { return target } } return nil default: return nil } } }