2019-09-23 21:30:20 +03:00

204 lines
5.4 KiB
Go

// Copyright 2019 The Go Cloud Development Kit 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
//
// https://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 gcerr provides an error type for Go CDK APIs.
package gcerr
import (
"context"
"fmt"
"io"
"reflect"
"gocloud.dev/internal/retry"
"golang.org/x/xerrors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// An ErrorCode describes the error's category.
type ErrorCode int
const (
// Returned by the Code function on a nil error. It is not a valid
// code for an error.
OK ErrorCode = 0
// The error could not be categorized.
Unknown ErrorCode = 1
// The resource was not found.
NotFound ErrorCode = 2
// The resource exists, but it should not.
AlreadyExists ErrorCode = 3
// A value given to a Go CDK API is incorrect.
InvalidArgument ErrorCode = 4
// Something unexpected happened. Internal errors always indicate
// bugs in the Go CDK (or possibly the underlying service).
Internal ErrorCode = 5
// The feature is not implemented.
Unimplemented ErrorCode = 6
// The system was in the wrong state.
FailedPrecondition ErrorCode = 7
// The caller does not have permission to execute the specified operation.
PermissionDenied ErrorCode = 8
// Some resource has been exhausted, typically because a service resource limit
// has been reached.
ResourceExhausted ErrorCode = 9
// The operation was canceled.
Canceled ErrorCode = 10
// The operation timed out.
DeadlineExceeded ErrorCode = 11
)
// When adding a new error code, try to use the names defined in google.golang.org/grpc/codes.
// Do not change the numbers assigned to codes: past values may be stored in metric databases.
// Call "go generate" whenever you change the above list of error codes.
// To get stringer:
// go get golang.org/x/tools/cmd/stringer
// Make sure $GOPATH/bin or $GOBIN in on your path.
//go:generate stringer -type=ErrorCode
// An Error describes a Go CDK error.
type Error struct {
Code ErrorCode
msg string
frame xerrors.Frame
err error
}
func (e *Error) Error() string {
return fmt.Sprint(e)
}
func (e *Error) Format(s fmt.State, c rune) {
xerrors.FormatError(e, s, c)
}
func (e *Error) FormatError(p xerrors.Printer) (next error) {
if e.msg == "" {
p.Printf("code=%v", e.Code)
} else {
p.Printf("%s (code=%v)", e.msg, e.Code)
}
e.frame.Format(p)
return e.err
}
// Unwrap returns the error underlying the receiver, which may be nil.
func (e *Error) Unwrap() error {
return e.err
}
// New returns a new error with the given code, underlying error and message. Pass 1
// for the call depth if New is called from the function raising the error; pass 2 if
// it is called from a helper function that was invoked by the original function; and
// so on.
func New(c ErrorCode, err error, callDepth int, msg string) *Error {
return &Error{
Code: c,
msg: msg,
frame: xerrors.Caller(callDepth),
err: err,
}
}
// Newf uses format and args to format a message, then calls New.
func Newf(c ErrorCode, err error, format string, args ...interface{}) *Error {
return New(c, err, 2, fmt.Sprintf(format, args...))
}
// DoNotWrap reports whether an error should not be wrapped in the Error
// type from this package.
// It returns true if err is a retry error, a context error, io.EOF, or if it wraps
// one of those.
func DoNotWrap(err error) bool {
if xerrors.Is(err, io.EOF) {
return true
}
if xerrors.Is(err, context.Canceled) {
return true
}
if xerrors.Is(err, context.DeadlineExceeded) {
return true
}
var r *retry.ContextError
if xerrors.As(err, &r) {
return true
}
return false
}
// GRPCCode extracts the gRPC status code and converts it into an ErrorCode.
// It returns Unknown if the error isn't from gRPC.
func GRPCCode(err error) ErrorCode {
switch status.Code(err) {
case codes.NotFound:
return NotFound
case codes.AlreadyExists:
return AlreadyExists
case codes.InvalidArgument:
return InvalidArgument
case codes.Internal:
return Internal
case codes.Unimplemented:
return Unimplemented
case codes.FailedPrecondition:
return FailedPrecondition
case codes.PermissionDenied:
return PermissionDenied
case codes.ResourceExhausted:
return ResourceExhausted
case codes.Canceled:
return Canceled
case codes.DeadlineExceeded:
return DeadlineExceeded
default:
return Unknown
}
}
// ErrorAs is a helper for the ErrorAs method of an API's portable type.
// It performs some initial nil checks, and does a single level of unwrapping
// when err is a *gcerr.Error. Then it calls its errorAs argument, which should
// be a driver implementation of ErrorAs.
func ErrorAs(err error, target interface{}, errorAs func(error, interface{}) bool) bool {
if err == nil {
return false
}
if target == nil {
panic("ErrorAs target cannot be nil")
}
val := reflect.ValueOf(target)
if val.Type().Kind() != reflect.Ptr || val.IsNil() {
panic("ErrorAs target must be a non-nil pointer")
}
if e, ok := err.(*Error); ok {
err = e.Unwrap()
}
return errorAs(err, target)
}