...

Source file src/github.com/letsencrypt/boulder/grpc/errors.go

Documentation: github.com/letsencrypt/boulder/grpc

     1  package grpc
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	"google.golang.org/grpc"
    12  	"google.golang.org/grpc/metadata"
    13  	"google.golang.org/grpc/status"
    14  
    15  	berrors "github.com/letsencrypt/boulder/errors"
    16  )
    17  
    18  // wrapError wraps the internal error types we use for transport across the gRPC
    19  // layer and appends an appropriate errortype to the gRPC trailer via the provided
    20  // context. errors.BoulderError error types are encoded using the grpc/metadata
    21  // in the context.Context for the RPC which is considered to be the 'proper'
    22  // method of encoding custom error types (grpc/grpc#4543 and grpc/grpc-go#478)
    23  func wrapError(ctx context.Context, appErr error) error {
    24  	if appErr == nil {
    25  		return nil
    26  	}
    27  
    28  	var berr *berrors.BoulderError
    29  	if errors.As(appErr, &berr) {
    30  		pairs := []string{
    31  			"errortype", strconv.Itoa(int(berr.Type)),
    32  		}
    33  
    34  		// If there are suberrors then extend the metadata pairs to include the JSON
    35  		// marshaling of the suberrors. Errors in marshaling are not ignored and
    36  		// instead result in a return of an explicit InternalServerError and not
    37  		// a wrapped error missing suberrors.
    38  		if len(berr.SubErrors) > 0 {
    39  			jsonSubErrs, err := json.Marshal(berr.SubErrors)
    40  			if err != nil {
    41  				return berrors.InternalServerError(
    42  					"error marshaling json SubErrors, orig error %q", err)
    43  			}
    44  			pairs = append(pairs, "suberrors", string(jsonSubErrs))
    45  		}
    46  
    47  		// If there is a RetryAfter value then extend the metadata pairs to
    48  		// include the value.
    49  		if berr.RetryAfter != 0 {
    50  			pairs = append(pairs, "retryafter", berr.RetryAfter.String())
    51  		}
    52  
    53  		err := grpc.SetTrailer(ctx, metadata.Pairs(pairs...))
    54  		if err != nil {
    55  			return berrors.InternalServerError(
    56  				"error setting gRPC error metadata, orig error %q", appErr)
    57  		}
    58  	}
    59  
    60  	return appErr
    61  }
    62  
    63  // unwrapError unwraps errors returned from gRPC client calls which were wrapped
    64  // with wrapError to their proper internal error type. If the provided metadata
    65  // object has an "errortype" field, that will be used to set the type of the
    66  // error.
    67  func unwrapError(err error, md metadata.MD) error {
    68  	if err == nil {
    69  		return nil
    70  	}
    71  
    72  	errTypeStrs, ok := md["errortype"]
    73  	if !ok {
    74  		return err
    75  	}
    76  
    77  	inErrMsg := status.Convert(err).Message()
    78  	if len(errTypeStrs) != 1 {
    79  		return berrors.InternalServerError(
    80  			"multiple 'errortype' metadata, wrapped error %q",
    81  			inErrMsg,
    82  		)
    83  	}
    84  
    85  	inErrType, decErr := strconv.Atoi(errTypeStrs[0])
    86  	if decErr != nil {
    87  		return berrors.InternalServerError(
    88  			"failed to decode error type, decoding error %q, wrapped error %q",
    89  			decErr,
    90  			inErrMsg,
    91  		)
    92  	}
    93  	inErr := berrors.New(berrors.ErrorType(inErrType), inErrMsg)
    94  	var outErr *berrors.BoulderError
    95  	if !errors.As(inErr, &outErr) {
    96  		return fmt.Errorf(
    97  			"expected type of inErr to be %T got %T: %q",
    98  			outErr,
    99  			inErr,
   100  			inErr.Error(),
   101  		)
   102  	}
   103  
   104  	subErrorsVal, ok := md["suberrors"]
   105  	if ok {
   106  		if len(subErrorsVal) != 1 {
   107  			return berrors.InternalServerError(
   108  				"multiple 'suberrors' in metadata, wrapped error %q",
   109  				inErrMsg,
   110  			)
   111  		}
   112  
   113  		unmarshalErr := json.Unmarshal([]byte(subErrorsVal[0]), &outErr.SubErrors)
   114  		if unmarshalErr != nil {
   115  			return berrors.InternalServerError(
   116  				"JSON unmarshaling 'suberrors' %q, wrapped error %q: %s",
   117  				subErrorsVal[0],
   118  				inErrMsg,
   119  				unmarshalErr,
   120  			)
   121  		}
   122  	}
   123  
   124  	retryAfterVal, ok := md["retryafter"]
   125  	if ok {
   126  		if len(retryAfterVal) != 1 {
   127  			return berrors.InternalServerError(
   128  				"multiple 'retryafter' in metadata, wrapped error %q",
   129  				inErrMsg,
   130  			)
   131  		}
   132  		var parseErr error
   133  		outErr.RetryAfter, parseErr = time.ParseDuration(retryAfterVal[0])
   134  		if parseErr != nil {
   135  			return berrors.InternalServerError(
   136  				"parsing 'retryafter' as int64, wrapped error %q, parsing error: %s",
   137  				inErrMsg,
   138  				parseErr,
   139  			)
   140  		}
   141  	}
   142  	return outErr
   143  }
   144  

View as plain text