github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/common/rpc/errors.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/hashicorp/go-multierror"
     8  	"google.golang.org/grpc/codes"
     9  	"google.golang.org/grpc/status"
    10  
    11  	"github.com/onflow/flow-go/module/state_synchronization/indexer"
    12  	"github.com/onflow/flow-go/storage"
    13  )
    14  
    15  // ConvertError converts a generic error into a grpc status error. The input may either
    16  // be a status.Error already, or standard error type. Any error that matches on of the
    17  // common status code mappings will be converted, all unmatched errors will be converted
    18  // to the provided defaultCode.
    19  func ConvertError(err error, msg string, defaultCode codes.Code) error {
    20  	if err == nil {
    21  		return nil
    22  	}
    23  
    24  	// Handle multierrors separately
    25  	if multiErr, ok := err.(*multierror.Error); ok {
    26  		return ConvertMultiError(multiErr, msg, defaultCode)
    27  	}
    28  
    29  	// Already converted
    30  	if status.Code(err) != codes.Unknown {
    31  		return err
    32  	}
    33  
    34  	if msg != "" {
    35  		msg += ": "
    36  	}
    37  
    38  	var returnCode codes.Code
    39  	switch {
    40  	case errors.Is(err, context.Canceled):
    41  		returnCode = codes.Canceled
    42  	case errors.Is(err, context.DeadlineExceeded):
    43  		returnCode = codes.DeadlineExceeded
    44  	default:
    45  		returnCode = defaultCode
    46  	}
    47  
    48  	return status.Errorf(returnCode, "%s%v", msg, err)
    49  }
    50  
    51  // ConvertStorageError converts a generic error into a grpc status error, converting storage errors
    52  // into codes.NotFound
    53  func ConvertStorageError(err error) error {
    54  	if err == nil {
    55  		return nil
    56  	}
    57  
    58  	// Already converted
    59  	if status.Code(err) == codes.NotFound {
    60  		return err
    61  	}
    62  
    63  	if errors.Is(err, storage.ErrNotFound) {
    64  		return status.Errorf(codes.NotFound, "not found: %v", err)
    65  	}
    66  
    67  	return status.Errorf(codes.Internal, "failed to find: %v", err)
    68  }
    69  
    70  // ConvertIndexError converts errors related to index and storage to appropriate gRPC status errors.
    71  // If the error is nil, it returns nil. If the error is not recognized, it falls back to ConvertError
    72  // with the provided default message and Internal gRPC code.
    73  func ConvertIndexError(err error, height uint64, defaultMsg string) error {
    74  	if err == nil {
    75  		return nil
    76  	}
    77  
    78  	if errors.Is(err, indexer.ErrIndexNotInitialized) {
    79  		return status.Errorf(codes.FailedPrecondition, "data for block is not available: %v", err)
    80  	}
    81  
    82  	if errors.Is(err, storage.ErrHeightNotIndexed) {
    83  		return status.Errorf(codes.OutOfRange, "data for block height %d is not available", height)
    84  	}
    85  
    86  	if errors.Is(err, storage.ErrNotFound) {
    87  		return status.Errorf(codes.NotFound, "data not found: %v", err)
    88  	}
    89  
    90  	return ConvertError(err, defaultMsg, codes.Internal)
    91  }
    92  
    93  // ConvertMultiError converts a multierror to a grpc status error.
    94  // If the errors have related status codes, the common code is returned, otherwise defaultCode is used.
    95  func ConvertMultiError(err *multierror.Error, msg string, defaultCode codes.Code) error {
    96  	allErrors := err.WrappedErrors()
    97  	if len(allErrors) == 0 {
    98  		return nil
    99  	}
   100  
   101  	if msg != "" {
   102  		msg += ": "
   103  	}
   104  
   105  	// get a list of all of status codes
   106  	allCodes := make(map[codes.Code]struct{})
   107  	for _, err := range allErrors {
   108  		allCodes[status.Code(err)] = struct{}{}
   109  	}
   110  
   111  	// if they all match, return that
   112  	if len(allCodes) == 1 {
   113  		code := status.Code(allErrors[0])
   114  		return status.Errorf(code, "%s%v", msg, err)
   115  	}
   116  
   117  	// if they mostly match, ignore Unavailable and DeadlineExceeded since any other code is
   118  	// more descriptive
   119  	if len(allCodes) == 2 {
   120  		if _, ok := allCodes[codes.Unavailable]; ok {
   121  			delete(allCodes, codes.Unavailable)
   122  			for code := range allCodes {
   123  				return status.Errorf(code, "%s%v", msg, err)
   124  			}
   125  		}
   126  		if _, ok := allCodes[codes.DeadlineExceeded]; ok {
   127  			delete(allCodes, codes.DeadlineExceeded)
   128  			for code := range allCodes {
   129  				return status.Errorf(code, "%s%v", msg, err)
   130  			}
   131  		}
   132  	}
   133  
   134  	// otherwise, return the default code
   135  	return status.Errorf(defaultCode, "%s%v", msg, err)
   136  }