use actix_web::{http::StatusCode, HttpResponse, ResponseError}; use tracing_error::SpanTrace; pub(crate) struct Error { context: SpanTrace, kind: UploadError, } impl std::fmt::Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "{}", self.kind) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "{}", self.kind)?; std::fmt::Display::fmt(&self.context, f) } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.kind.source() } } impl From for Error where UploadError: From, { fn from(error: T) -> Self { Error { kind: UploadError::from(error), context: SpanTrace::capture(), } } } impl From> for Error { fn from(e: sled::transaction::TransactionError) -> Self { match e { sled::transaction::TransactionError::Abort(t) => t, sled::transaction::TransactionError::Storage(e) => e.into(), } } } #[derive(Debug, thiserror::Error)] pub(crate) enum UploadError { #[error("Couln't upload file, {0}")] Upload(#[from] actix_form_data::Error), #[error("Error in DB, {0}")] Db(#[from] sled::Error), #[error("Error parsing string, {0}")] ParseString(#[from] std::string::FromUtf8Error), #[error("Error interacting with filesystem, {0}")] Io(#[from] std::io::Error), #[error(transparent)] PathGenerator(#[from] storage_path_generator::PathError), #[error(transparent)] StripPrefix(#[from] std::path::StripPrefixError), #[error(transparent)] FileStore(#[from] crate::store::file_store::FileError), #[cfg(feature = "object-storage")] #[error(transparent)] ObjectStore(#[from] crate::store::object_store::ObjectError), #[error("Provided process path is invalid")] ParsePath, #[error("Failed to acquire the semaphore")] Semaphore, #[error("Panic in blocking operation")] Canceled, #[error("No files present in upload")] NoFiles, #[error("Requested a file that doesn't exist")] MissingAlias, #[error("Alias directed to missing file")] MissingFile, #[error("Provided token did not match expected token")] InvalidToken, #[error("Unsupported image format")] UnsupportedFormat, #[error("Invalid media dimensions")] Dimensions, #[error("Unable to download image, bad response {0}")] Download(actix_web::http::StatusCode), #[error("Unable to download image, {0}")] Payload(#[from] awc::error::PayloadError), #[error("Unable to send request, {0}")] SendRequest(String), #[error("No filename provided in request")] MissingFilename, #[error("Error converting Path to String")] Path, #[error("Tried to save an image with an already-taken name")] DuplicateAlias, #[error("{0}")] Json(#[from] serde_json::Error), #[error("Range header not satisfiable")] Range, #[error(transparent)] Limit(#[from] super::LimitError), } impl From for UploadError { fn from(e: awc::error::SendRequestError) -> Self { UploadError::SendRequest(e.to_string()) } } impl From for UploadError { fn from(_: actix_web::error::BlockingError) -> Self { UploadError::Canceled } } impl From for UploadError { fn from(_: tokio::sync::AcquireError) -> Self { UploadError::Semaphore } } impl ResponseError for Error { fn status_code(&self) -> StatusCode { match self.kind { UploadError::DuplicateAlias | UploadError::Limit(_) | UploadError::NoFiles | UploadError::Upload(_) => StatusCode::BAD_REQUEST, UploadError::MissingAlias | UploadError::MissingFilename => StatusCode::NOT_FOUND, UploadError::InvalidToken => StatusCode::FORBIDDEN, UploadError::Range => StatusCode::RANGE_NOT_SATISFIABLE, _ => StatusCode::INTERNAL_SERVER_ERROR, } } fn error_response(&self) -> HttpResponse { HttpResponse::build(self.status_code()) .content_type("application/json") .body( serde_json::to_string(&serde_json::json!({ "msg": self.kind.to_string() })) .unwrap_or_else(|_| r#"{"msg":"Request failed"}"#.to_string()), ) } }