github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/errdefs/grpc.go (about)

     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  	"strings"
    22  
    23  	"github.com/pkg/errors"
    24  	"google.golang.org/grpc/codes"
    25  	"google.golang.org/grpc/status"
    26  )
    27  
    28  // ToGRPC will attempt to map the backend containerd error into a grpc error,
    29  // using the original error message as a description.
    30  //
    31  // Further information may be extracted from certain errors depending on their
    32  // type.
    33  //
    34  // If the error is unmapped, the original error will be returned to be handled
    35  // by the regular grpc error handling stack.
    36  func ToGRPC(err error) error {
    37  	if err == nil {
    38  		return nil
    39  	}
    40  
    41  	if isGRPCError(err) {
    42  		// error has already been mapped to grpc
    43  		return err
    44  	}
    45  
    46  	switch {
    47  	case IsInvalidArgument(err):
    48  		return status.Errorf(codes.InvalidArgument, err.Error())
    49  	case IsNotFound(err):
    50  		return status.Errorf(codes.NotFound, err.Error())
    51  	case IsAlreadyExists(err):
    52  		return status.Errorf(codes.AlreadyExists, err.Error())
    53  	case IsFailedPrecondition(err):
    54  		return status.Errorf(codes.FailedPrecondition, err.Error())
    55  	case IsUnavailable(err):
    56  		return status.Errorf(codes.Unavailable, err.Error())
    57  	case IsNotImplemented(err):
    58  		return status.Errorf(codes.Unimplemented, err.Error())
    59  	case IsCanceled(err):
    60  		return status.Errorf(codes.Canceled, err.Error())
    61  	case IsDeadlineExceeded(err):
    62  		return status.Errorf(codes.DeadlineExceeded, err.Error())
    63  	}
    64  
    65  	return err
    66  }
    67  
    68  // ToGRPCf maps the error to grpc error codes, assembling the formatting string
    69  // and combining it with the target error string.
    70  //
    71  // This is equivalent to errors.ToGRPC(errors.Wrapf(err, format, args...))
    72  func ToGRPCf(err error, format string, args ...interface{}) error {
    73  	return ToGRPC(errors.Wrapf(err, format, args...))
    74  }
    75  
    76  // FromGRPC returns the underlying error from a grpc service based on the grpc error code
    77  func FromGRPC(err error) error {
    78  	if err == nil {
    79  		return nil
    80  	}
    81  
    82  	var cls error // divide these into error classes, becomes the cause
    83  
    84  	switch code(err) {
    85  	case codes.InvalidArgument:
    86  		cls = ErrInvalidArgument
    87  	case codes.AlreadyExists:
    88  		cls = ErrAlreadyExists
    89  	case codes.NotFound:
    90  		cls = ErrNotFound
    91  	case codes.Unavailable:
    92  		cls = ErrUnavailable
    93  	case codes.FailedPrecondition:
    94  		cls = ErrFailedPrecondition
    95  	case codes.Unimplemented:
    96  		cls = ErrNotImplemented
    97  	case codes.Canceled:
    98  		cls = context.Canceled
    99  	case codes.DeadlineExceeded:
   100  		cls = context.DeadlineExceeded
   101  	default:
   102  		cls = ErrUnknown
   103  	}
   104  
   105  	msg := rebaseMessage(cls, err)
   106  	if msg != "" {
   107  		err = errors.Wrap(cls, msg)
   108  	} else {
   109  		err = errors.WithStack(cls)
   110  	}
   111  
   112  	return err
   113  }
   114  
   115  // rebaseMessage removes the repeats for an error at the end of an error
   116  // string. This will happen when taking an error over grpc then remapping it.
   117  //
   118  // Effectively, we just remove the string of cls from the end of err if it
   119  // appears there.
   120  func rebaseMessage(cls error, err error) string {
   121  	desc := errDesc(err)
   122  	clss := cls.Error()
   123  	if desc == clss {
   124  		return ""
   125  	}
   126  
   127  	return strings.TrimSuffix(desc, ": "+clss)
   128  }
   129  
   130  func isGRPCError(err error) bool {
   131  	_, ok := status.FromError(err)
   132  	return ok
   133  }
   134  
   135  func code(err error) codes.Code {
   136  	if s, ok := status.FromError(err); ok {
   137  		return s.Code()
   138  	}
   139  	return codes.Unknown
   140  }
   141  
   142  func errDesc(err error) string {
   143  	if s, ok := status.FromError(err); ok {
   144  		return s.Message()
   145  	}
   146  	return err.Error()
   147  }