package lance import ( "encoding/json" "errors" "net/http" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/s3api/s3tables" ) // Lance Namespace error codes. The spec numbers them, so a client switches on // the code rather than parsing the message. const ( codeUnsupported = 0 codeNamespaceNotFound = 1 codeNamespaceAlreadyExists = 2 codeNamespaceNotEmpty = 3 codeTableNotFound = 4 codeTableAlreadyExists = 5 codeTableVersionNotFound = 11 codeInvalidInput = 13 codeConcurrentModification = 14 codePermissionDenied = 15 codeUnauthenticated = 16 codeInternal = 18 ) type errorResponse struct { Error string `json:"error,omitempty"` Code int `json:"code"` Detail string `json:"detail,omitempty"` Instance string `json:"instance,omitempty"` } func writeJSON(w http.ResponseWriter, status int, body interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if body == nil { return } if err := json.NewEncoder(w).Encode(body); err != nil { glog.Warningf("lance: failed to encode response: %v", err) } } func writeError(w http.ResponseWriter, r *http.Request, status, code int, message string) { instance := "" if r != nil { instance = r.URL.Path } writeJSON(w, status, errorResponse{Error: message, Code: code, Instance: instance}) } // writeStorageError translates an S3 Tables storage error into the Lance error // model. A table bucket is the first namespace level here, so a missing bucket // is a missing namespace rather than a missing catalog. func writeStorageError(w http.ResponseWriter, r *http.Request, err error) { var storageErr *s3tables.S3TablesError if !errors.As(err, &storageErr) { writeError(w, r, http.StatusInternalServerError, codeInternal, err.Error()) return } status, code := http.StatusInternalServerError, codeInternal switch storageErr.Type { case s3tables.ErrCodeNoSuchBucket, s3tables.ErrCodeNoSuchNamespace: status, code = http.StatusNotFound, codeNamespaceNotFound case s3tables.ErrCodeNoSuchTable, s3tables.ErrCodeNoSuchView: status, code = http.StatusNotFound, codeTableNotFound case s3tables.ErrCodeBucketAlreadyExists, s3tables.ErrCodeNamespaceAlreadyExists: status, code = http.StatusConflict, codeNamespaceAlreadyExists case s3tables.ErrCodeTableAlreadyExists, s3tables.ErrCodeViewAlreadyExists: status, code = http.StatusConflict, codeTableAlreadyExists case s3tables.ErrCodeBucketNotEmpty, s3tables.ErrCodeNamespaceNotEmpty: status, code = http.StatusConflict, codeNamespaceNotEmpty case s3tables.ErrCodeConflict: status, code = http.StatusConflict, codeConcurrentModification case s3tables.ErrCodeAccessDenied: status, code = http.StatusForbidden, codePermissionDenied case s3tables.ErrCodeInvalidRequest, s3tables.ErrCodeInvalidIcebergLayout: status, code = http.StatusBadRequest, codeInvalidInput } writeError(w, r, status, code, storageErr.Message) } // isNotFound reports whether a storage error means the object is absent, so // exists-style handlers can answer without a second lookup. func isNotFound(err error) bool { var storageErr *s3tables.S3TablesError if !errors.As(err, &storageErr) { return false } switch storageErr.Type { case s3tables.ErrCodeNoSuchBucket, s3tables.ErrCodeNoSuchNamespace, s3tables.ErrCodeNoSuchTable, s3tables.ErrCodeNoSuchView: return true } return false }