dotcloud/docker

View on GitHub
api/server/httpstatus/status.go

Summary

Maintainability
A
40 mins
Test Coverage
package httpstatus // import "github.com/docker/docker/api/server/httpstatus"

import (
    "context"
    "fmt"
    "net/http"

    cerrdefs "github.com/containerd/errdefs"
    "github.com/containerd/log"
    "github.com/docker/distribution/registry/api/errcode"
    "github.com/docker/docker/errdefs"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

type causer interface {
    Cause() error
}

// FromError retrieves status code from error message.
func FromError(err error) int {
    if err == nil {
        log.G(context.TODO()).WithError(err).Error("unexpected HTTP error handling")
        return http.StatusInternalServerError
    }

    // Stop right there
    // Are you sure you should be adding a new error class here? Do one of the existing ones work?

    // Note that the below functions are already checking the error causal chain for matches.
    switch {
    case errdefs.IsNotFound(err):
        return http.StatusNotFound
    case errdefs.IsInvalidParameter(err):
        return http.StatusBadRequest
    case errdefs.IsConflict(err):
        return http.StatusConflict
    case errdefs.IsUnauthorized(err):
        return http.StatusUnauthorized
    case errdefs.IsUnavailable(err):
        return http.StatusServiceUnavailable
    case errdefs.IsForbidden(err):
        return http.StatusForbidden
    case errdefs.IsNotModified(err):
        return http.StatusNotModified
    case errdefs.IsNotImplemented(err):
        return http.StatusNotImplemented
    case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
        return http.StatusInternalServerError
    default:
        if statusCode := statusCodeFromGRPCError(err); statusCode != http.StatusInternalServerError {
            return statusCode
        }
        if statusCode := statusCodeFromContainerdError(err); statusCode != http.StatusInternalServerError {
            return statusCode
        }
        if statusCode := statusCodeFromDistributionError(err); statusCode != http.StatusInternalServerError {
            return statusCode
        }
        if e, ok := err.(causer); ok {
            return FromError(e.Cause())
        }

        log.G(context.TODO()).WithFields(log.Fields{
            "module":     "api",
            "error":      err,
            "error_type": fmt.Sprintf("%T", err),
        }).Debug("FIXME: Got an API for which error does not match any expected type!!!")

        return http.StatusInternalServerError
    }
}

// statusCodeFromGRPCError returns status code according to gRPC error
func statusCodeFromGRPCError(err error) int {
    switch status.Code(err) {
    case codes.InvalidArgument: // code 3
        return http.StatusBadRequest
    case codes.NotFound: // code 5
        return http.StatusNotFound
    case codes.AlreadyExists: // code 6
        return http.StatusConflict
    case codes.PermissionDenied: // code 7
        return http.StatusForbidden
    case codes.FailedPrecondition: // code 9
        return http.StatusBadRequest
    case codes.Unauthenticated: // code 16
        return http.StatusUnauthorized
    case codes.OutOfRange: // code 11
        return http.StatusBadRequest
    case codes.Unimplemented: // code 12
        return http.StatusNotImplemented
    case codes.Unavailable: // code 14
        return http.StatusServiceUnavailable
    default:
        // codes.Canceled(1)
        // codes.Unknown(2)
        // codes.DeadlineExceeded(4)
        // codes.ResourceExhausted(8)
        // codes.Aborted(10)
        // codes.Internal(13)
        // codes.DataLoss(15)
        return http.StatusInternalServerError
    }
}

// statusCodeFromDistributionError returns status code according to registry errcode
// code is loosely based on errcode.ServeJSON() in docker/distribution
func statusCodeFromDistributionError(err error) int {
    switch errs := err.(type) {
    case errcode.Errors:
        if len(errs) < 1 {
            return http.StatusInternalServerError
        }
        if _, ok := errs[0].(errcode.ErrorCoder); ok {
            return statusCodeFromDistributionError(errs[0])
        }
    case errcode.ErrorCoder:
        return errs.ErrorCode().Descriptor().HTTPStatusCode
    }
    return http.StatusInternalServerError
}

// statusCodeFromContainerdError returns status code for containerd errors when
// consumed directly (not through gRPC)
func statusCodeFromContainerdError(err error) int {
    switch {
    case cerrdefs.IsInvalidArgument(err):
        return http.StatusBadRequest
    case cerrdefs.IsNotFound(err):
        return http.StatusNotFound
    case cerrdefs.IsAlreadyExists(err):
        return http.StatusConflict
    case cerrdefs.IsFailedPrecondition(err):
        return http.StatusPreconditionFailed
    case cerrdefs.IsUnavailable(err):
        return http.StatusServiceUnavailable
    case cerrdefs.IsNotImplemented(err):
        return http.StatusNotImplemented
    default:
        return http.StatusInternalServerError
    }
}