github.com/moby/docker@v26.1.3+incompatible/api/server/httpstatus/status.go (about)

     1  package httpstatus // import "github.com/docker/docker/api/server/httpstatus"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	cerrdefs "github.com/containerd/containerd/errdefs"
     9  	"github.com/containerd/log"
    10  	"github.com/docker/distribution/registry/api/errcode"
    11  	"github.com/docker/docker/errdefs"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/status"
    14  )
    15  
    16  type causer interface {
    17  	Cause() error
    18  }
    19  
    20  // FromError retrieves status code from error message.
    21  func FromError(err error) int {
    22  	if err == nil {
    23  		log.G(context.TODO()).WithError(err).Error("unexpected HTTP error handling")
    24  		return http.StatusInternalServerError
    25  	}
    26  
    27  	var statusCode int
    28  
    29  	// Stop right there
    30  	// Are you sure you should be adding a new error class here? Do one of the existing ones work?
    31  
    32  	// Note that the below functions are already checking the error causal chain for matches.
    33  	switch {
    34  	case errdefs.IsNotFound(err):
    35  		statusCode = http.StatusNotFound
    36  	case errdefs.IsInvalidParameter(err):
    37  		statusCode = http.StatusBadRequest
    38  	case errdefs.IsConflict(err):
    39  		statusCode = http.StatusConflict
    40  	case errdefs.IsUnauthorized(err):
    41  		statusCode = http.StatusUnauthorized
    42  	case errdefs.IsUnavailable(err):
    43  		statusCode = http.StatusServiceUnavailable
    44  	case errdefs.IsForbidden(err):
    45  		statusCode = http.StatusForbidden
    46  	case errdefs.IsNotModified(err):
    47  		statusCode = http.StatusNotModified
    48  	case errdefs.IsNotImplemented(err):
    49  		statusCode = http.StatusNotImplemented
    50  	case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
    51  		statusCode = http.StatusInternalServerError
    52  	default:
    53  		statusCode = statusCodeFromGRPCError(err)
    54  		if statusCode != http.StatusInternalServerError {
    55  			return statusCode
    56  		}
    57  		statusCode = statusCodeFromContainerdError(err)
    58  		if statusCode != http.StatusInternalServerError {
    59  			return statusCode
    60  		}
    61  		statusCode = statusCodeFromDistributionError(err)
    62  		if statusCode != http.StatusInternalServerError {
    63  			return statusCode
    64  		}
    65  		if e, ok := err.(causer); ok {
    66  			return FromError(e.Cause())
    67  		}
    68  
    69  		log.G(context.TODO()).WithFields(log.Fields{
    70  			"module":     "api",
    71  			"error":      err,
    72  			"error_type": fmt.Sprintf("%T", err),
    73  		}).Debug("FIXME: Got an API for which error does not match any expected type!!!")
    74  	}
    75  
    76  	if statusCode == 0 {
    77  		statusCode = http.StatusInternalServerError
    78  	}
    79  
    80  	return statusCode
    81  }
    82  
    83  // statusCodeFromGRPCError returns status code according to gRPC error
    84  func statusCodeFromGRPCError(err error) int {
    85  	switch status.Code(err) {
    86  	case codes.InvalidArgument: // code 3
    87  		return http.StatusBadRequest
    88  	case codes.NotFound: // code 5
    89  		return http.StatusNotFound
    90  	case codes.AlreadyExists: // code 6
    91  		return http.StatusConflict
    92  	case codes.PermissionDenied: // code 7
    93  		return http.StatusForbidden
    94  	case codes.FailedPrecondition: // code 9
    95  		return http.StatusBadRequest
    96  	case codes.Unauthenticated: // code 16
    97  		return http.StatusUnauthorized
    98  	case codes.OutOfRange: // code 11
    99  		return http.StatusBadRequest
   100  	case codes.Unimplemented: // code 12
   101  		return http.StatusNotImplemented
   102  	case codes.Unavailable: // code 14
   103  		return http.StatusServiceUnavailable
   104  	default:
   105  		// codes.Canceled(1)
   106  		// codes.Unknown(2)
   107  		// codes.DeadlineExceeded(4)
   108  		// codes.ResourceExhausted(8)
   109  		// codes.Aborted(10)
   110  		// codes.Internal(13)
   111  		// codes.DataLoss(15)
   112  		return http.StatusInternalServerError
   113  	}
   114  }
   115  
   116  // statusCodeFromDistributionError returns status code according to registry errcode
   117  // code is loosely based on errcode.ServeJSON() in docker/distribution
   118  func statusCodeFromDistributionError(err error) int {
   119  	switch errs := err.(type) {
   120  	case errcode.Errors:
   121  		if len(errs) < 1 {
   122  			return http.StatusInternalServerError
   123  		}
   124  		if _, ok := errs[0].(errcode.ErrorCoder); ok {
   125  			return statusCodeFromDistributionError(errs[0])
   126  		}
   127  	case errcode.ErrorCoder:
   128  		return errs.ErrorCode().Descriptor().HTTPStatusCode
   129  	}
   130  	return http.StatusInternalServerError
   131  }
   132  
   133  // statusCodeFromContainerdError returns status code for containerd errors when
   134  // consumed directly (not through gRPC)
   135  func statusCodeFromContainerdError(err error) int {
   136  	switch {
   137  	case cerrdefs.IsInvalidArgument(err):
   138  		return http.StatusBadRequest
   139  	case cerrdefs.IsNotFound(err):
   140  		return http.StatusNotFound
   141  	case cerrdefs.IsAlreadyExists(err):
   142  		return http.StatusConflict
   143  	case cerrdefs.IsFailedPrecondition(err):
   144  		return http.StatusPreconditionFailed
   145  	case cerrdefs.IsUnavailable(err):
   146  		return http.StatusServiceUnavailable
   147  	case cerrdefs.IsNotImplemented(err):
   148  		return http.StatusNotImplemented
   149  	default:
   150  		return http.StatusInternalServerError
   151  	}
   152  }