From ba3a23ed43f3d7d370dbdcb9e99c2d9a40ca979f Mon Sep 17 00:00:00 2001 From: asonix Date: Fri, 1 Sep 2023 20:50:10 -0500 Subject: [PATCH] Add error codes --- src/error.rs | 68 ++++++++++++++----- src/error_code.rs | 137 ++++++++++++++++++++++++++++++++++++++ src/exiftool.rs | 12 +++- src/ffmpeg.rs | 19 ++++++ src/lib.rs | 9 +-- src/magick.rs | 19 ++++++ src/process.rs | 14 ++++ src/queue.rs | 25 ++++--- src/queue/process.rs | 1 + src/repo.rs | 22 +++++- src/repo/sled.rs | 33 ++++++--- src/store.rs | 11 +++ src/store/file_store.rs | 16 ++++- src/store/object_store.rs | 25 ++++++- src/validate.rs | 15 +++++ 15 files changed, 380 insertions(+), 46 deletions(-) create mode 100644 src/error_code.rs diff --git a/src/error.rs b/src/error.rs index 991eb7b..38c481e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,8 @@ use actix_web::{http::StatusCode, HttpResponse, ResponseError}; use color_eyre::Report; +use crate::error_code::ErrorCode; + pub(crate) struct Error { inner: color_eyre::Report, } @@ -13,6 +15,12 @@ impl Error { pub(crate) fn root_cause(&self) -> &(dyn std::error::Error + 'static) { self.inner.root_cause() } + + pub(crate) fn error_code(&self) -> ErrorCode { + self.kind() + .map(|e| e.error_code()) + .unwrap_or(ErrorCode::UNKNOWN_ERROR) + } } impl std::fmt::Debug for Error { @@ -55,21 +63,12 @@ pub(crate) enum UploadError { #[error("Error in old repo")] OldRepo(#[from] crate::repo_04::RepoError), - #[error("Error parsing string")] - ParseString(#[from] std::string::FromUtf8Error), - #[error("Error interacting with filesystem")] Io(#[from] std::io::Error), #[error("Error validating upload")] Validation(#[from] crate::validate::ValidationError), - #[error("Error generating path")] - PathGenerator(#[from] storage_path_generator::PathError), - - #[error("Error stripping prefix")] - StripPrefix(#[from] std::path::StripPrefixError), - #[error("Error in store")] Store(#[source] crate::store::StoreError), @@ -127,11 +126,8 @@ pub(crate) enum UploadError { #[error("Tried to save an image with an already-taken name")] DuplicateAlias, - #[error("Error in json")] - Json(#[from] serde_json::Error), - - #[error("Error in cbor")] - Cbor(#[from] serde_cbor::Error), + #[error("Failed to serialize job")] + PushJob(#[source] serde_json::Error), #[error("Range header not satisfiable")] Range, @@ -143,6 +139,41 @@ pub(crate) enum UploadError { Timeout(#[from] crate::stream::TimeoutError), } +impl UploadError { + const fn error_code(&self) -> ErrorCode { + match self { + Self::Upload(_) => ErrorCode::FILE_UPLOAD_ERROR, + Self::Repo(e) => e.error_code(), + Self::OldRepo(_) => ErrorCode::OLD_REPO_ERROR, + Self::Io(_) => ErrorCode::IO_ERROR, + Self::Validation(e) => e.error_code(), + Self::Store(e) => e.error_code(), + Self::Ffmpeg(e) => e.error_code(), + Self::Magick(e) => e.error_code(), + Self::Exiftool(e) => e.error_code(), + Self::BuildClient(_) | Self::RequestMiddleware(_) | Self::Request(_) => { + ErrorCode::HTTP_CLIENT_ERROR + } + Self::Download(_) => ErrorCode::DOWNLOAD_FILE_ERROR, + Self::ReadOnly => ErrorCode::READ_ONLY, + Self::InvalidProcessExtension => ErrorCode::INVALID_FILE_EXTENSION, + Self::ParsePath => ErrorCode::INVALID_PROCESS_PATH, + Self::Semaphore => ErrorCode::PROCESS_SEMAPHORE_CLOSED, + Self::Canceled => ErrorCode::PANIC, + Self::NoFiles => ErrorCode::VALIDATE_NO_FILES, + Self::MissingAlias => ErrorCode::ALIAS_NOT_FOUND, + Self::MissingIdentifier => ErrorCode::LOST_FILE, + Self::InvalidToken => ErrorCode::INVALID_DELETE_TOKEN, + Self::UnsupportedProcessExtension => ErrorCode::INVALID_FILE_EXTENSION, + Self::DuplicateAlias => ErrorCode::DUPLICATE_ALIAS, + Self::PushJob(_) => todo!(), + Self::Range => ErrorCode::RANGE_NOT_SATISFIABLE, + Self::Limit(_) => ErrorCode::VALIDATE_FILE_SIZE, + Self::Timeout(_) => ErrorCode::STREAM_TOO_SLOW, + } + } +} + impl From for UploadError { fn from(_: actix_web::error::BlockingError) -> Self { UploadError::Canceled @@ -196,8 +227,13 @@ impl ResponseError for Error { HttpResponse::build(self.status_code()) .content_type("application/json") .body( - serde_json::to_string(&serde_json::json!({ "msg": self.root_cause().to_string() })) - .unwrap_or_else(|_| r#"{"msg":"Request failed"}"#.to_string()), + serde_json::to_string(&serde_json::json!({ + "msg": self.root_cause().to_string(), + "code": self.error_code() + })) + .unwrap_or_else(|_| { + r#"{"msg":"Request failed","code":"unknown-error"}"#.to_string() + }), ) } } diff --git a/src/error_code.rs b/src/error_code.rs new file mode 100644 index 0000000..67eaef2 --- /dev/null +++ b/src/error_code.rs @@ -0,0 +1,137 @@ +#[derive(Debug, serde::Serialize)] +#[serde(transparent)] +pub(crate) struct ErrorCode { + code: &'static str, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[serde(transparent)] +pub(crate) struct OwnedErrorCode { + code: String, +} + +impl ErrorCode { + pub(crate) fn into_owned(self) -> OwnedErrorCode { + OwnedErrorCode { + code: self.code.to_string(), + } + } + + pub(crate) const COMMAND_TIMEOUT: ErrorCode = ErrorCode { + code: "command-timeout", + }; + pub(crate) const COMMAND_ERROR: ErrorCode = ErrorCode { + code: "command-error", + }; + pub(crate) const COMMAND_FAILURE: ErrorCode = ErrorCode { + code: "command-failure", + }; + pub(crate) const OLD_REPO_ERROR: ErrorCode = ErrorCode { + code: "old-repo-error", + }; + pub(crate) const NOT_FOUND: ErrorCode = ErrorCode { code: "not-found" }; + pub(crate) const FILE_IO_ERROR: ErrorCode = ErrorCode { + code: "file-io-error", + }; + pub(crate) const PARSE_PATH_ERROR: ErrorCode = ErrorCode { + code: "parse-path-error", + }; + pub(crate) const FILE_EXISTS: ErrorCode = ErrorCode { + code: "file-exists", + }; + pub(crate) const FORMAT_FILE_ID_ERROR: ErrorCode = ErrorCode { + code: "format-file-id-error", + }; + pub(crate) const OBJECT_REQUEST_ERROR: ErrorCode = ErrorCode { + code: "object-request-error", + }; + pub(crate) const OBJECT_IO_ERROR: ErrorCode = ErrorCode { + code: "object-io-error", + }; + pub(crate) const PARSE_OBJECT_ID_ERROR: ErrorCode = ErrorCode { + code: "parse-object-id-error", + }; + pub(crate) const PANIC: ErrorCode = ErrorCode { code: "panic" }; + pub(crate) const ALREADY_CLAIMED: ErrorCode = ErrorCode { + code: "already-claimed", + }; + pub(crate) const SLED_ERROR: ErrorCode = ErrorCode { code: "sled-error" }; + pub(crate) const EXTRACT_DETAILS: ErrorCode = ErrorCode { + code: "extract-details", + }; + pub(crate) const EXTRACT_UPLOAD_RESULT: ErrorCode = ErrorCode { + code: "extract-upload-result", + }; + pub(crate) const CONFLICTED_RECORD: ErrorCode = ErrorCode { + code: "conflicted-record", + }; + pub(crate) const COMMAND_NOT_FOUND: ErrorCode = ErrorCode { + code: "command-not-found", + }; + pub(crate) const COMMAND_PERMISSION_DENIED: ErrorCode = ErrorCode { + code: "command-permission-denied", + }; + pub(crate) const FILE_UPLOAD_ERROR: ErrorCode = ErrorCode { + code: "file-upload-error", + }; + pub(crate) const IO_ERROR: ErrorCode = ErrorCode { code: "io-error" }; + pub(crate) const VALIDATE_WIDTH: ErrorCode = ErrorCode { + code: "validate-width", + }; + pub(crate) const VALIDATE_HEIGHT: ErrorCode = ErrorCode { + code: "validate-height", + }; + pub(crate) const VALIDATE_AREA: ErrorCode = ErrorCode { + code: "validate-area", + }; + pub(crate) const VALIDATE_FRAMES: ErrorCode = ErrorCode { + code: "validate-frames", + }; + pub(crate) const VALIDATE_FILE_EMPTY: ErrorCode = ErrorCode { + code: "validate-file-empty", + }; + pub(crate) const VALIDATE_FILE_SIZE: ErrorCode = ErrorCode { + code: "validate-file-size", + }; + pub(crate) const VIDEO_DISABLED: ErrorCode = ErrorCode { + code: "video-disabled", + }; + pub(crate) const HTTP_CLIENT_ERROR: ErrorCode = ErrorCode { + code: "http-client-error", + }; + pub(crate) const DOWNLOAD_FILE_ERROR: ErrorCode = ErrorCode { + code: "download-file-error", + }; + pub(crate) const READ_ONLY: ErrorCode = ErrorCode { code: "read-only" }; + pub(crate) const INVALID_FILE_EXTENSION: ErrorCode = ErrorCode { + code: "invalid-file-extension", + }; + pub(crate) const INVALID_PROCESS_PATH: ErrorCode = ErrorCode { + code: "invalid-process-path", + }; + pub(crate) const PROCESS_SEMAPHORE_CLOSED: ErrorCode = ErrorCode { + code: "process-semaphore-closed", + }; + pub(crate) const VALIDATE_NO_FILES: ErrorCode = ErrorCode { + code: "validate-no-files", + }; + pub(crate) const ALIAS_NOT_FOUND: ErrorCode = ErrorCode { + code: "alias-not-found", + }; + pub(crate) const LOST_FILE: ErrorCode = ErrorCode { code: "lost-file" }; + pub(crate) const INVALID_DELETE_TOKEN: ErrorCode = ErrorCode { + code: "invalid-delete-token", + }; + pub(crate) const DUPLICATE_ALIAS: ErrorCode = ErrorCode { + code: "duplicate-alias", + }; + pub(crate) const RANGE_NOT_SATISFIABLE: ErrorCode = ErrorCode { + code: "range-not-satisfiable", + }; + pub(crate) const STREAM_TOO_SLOW: ErrorCode = ErrorCode { + code: "stream-too-slow", + }; + pub(crate) const UNKNOWN_ERROR: ErrorCode = ErrorCode { + code: "unknown-error", + }; +} diff --git a/src/exiftool.rs b/src/exiftool.rs index 7b2f5f5..2000ff3 100644 --- a/src/exiftool.rs +++ b/src/exiftool.rs @@ -1,4 +1,7 @@ -use crate::process::{Process, ProcessError}; +use crate::{ + error_code::ErrorCode, + process::{Process, ProcessError}, +}; use actix_web::web::Bytes; use tokio::io::{AsyncRead, AsyncReadExt}; @@ -24,6 +27,13 @@ impl From for ExifError { } impl ExifError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::Process(e) => e.error_code(), + Self::Read(_) => ErrorCode::COMMAND_ERROR, + Self::CommandFailed(_) => ErrorCode::COMMAND_FAILURE, + } + } pub(crate) fn is_client_error(&self) -> bool { // if exiftool bails we probably have bad input matches!( diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs index b5a1def..3602b5f 100644 --- a/src/ffmpeg.rs +++ b/src/ffmpeg.rs @@ -1,4 +1,5 @@ use crate::{ + error_code::ErrorCode, formats::InternalVideoFormat, process::{Process, ProcessError}, store::{Store, StoreError}, @@ -63,6 +64,24 @@ impl From for FfMpegError { } impl FfMpegError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::CommandFailed(_) => ErrorCode::COMMAND_FAILURE, + Self::Store(s) => s.error_code(), + Self::Process(e) => e.error_code(), + Self::Read(_) + | Self::Write(_) + | Self::Json(_) + | Self::CreateDir(_) + | Self::ReadFile(_) + | Self::OpenFile(_) + | Self::CreateFile(_) + | Self::CloseFile(_) + | Self::RemoveFile(_) + | Self::Path => ErrorCode::COMMAND_ERROR, + } + } + pub(crate) fn is_client_error(&self) -> bool { // Failing validation or ffmpeg bailing probably means bad input matches!( diff --git a/src/lib.rs b/src/lib.rs index 74a3f09..b7faff8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod details; mod discover; mod either; mod error; +mod error_code; mod exiftool; mod ffmpeg; mod file; @@ -450,11 +451,11 @@ async fn claim_upload( }] }))) } - UploadResult::Failure { message } => Ok(HttpResponse::UnprocessableEntity().json( - &serde_json::json!({ + UploadResult::Failure { message, code } => Ok(HttpResponse::UnprocessableEntity() + .json(&serde_json::json!({ "msg": message, - }), - )), + "code": code, + }))), } } Err(_) => Ok(HttpResponse::NoContent().finish()), diff --git a/src/magick.rs b/src/magick.rs index c59546d..b9efaa6 100644 --- a/src/magick.rs +++ b/src/magick.rs @@ -1,4 +1,5 @@ use crate::{ + error_code::ErrorCode, formats::ProcessableFormat, process::{Process, ProcessError}, store::Store, @@ -57,6 +58,24 @@ impl From for MagickError { } impl MagickError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::CommandFailed(_) => ErrorCode::COMMAND_FAILURE, + Self::Store(e) => e.error_code(), + Self::Process(e) => e.error_code(), + Self::Json(_) + | Self::Read(_) + | Self::Write(_) + | Self::CreateFile(_) + | Self::CreateDir(_) + | Self::CloseFile(_) + | Self::RemoveFile(_) + | Self::Discover(_) + | Self::Empty + | Self::Path => ErrorCode::COMMAND_ERROR, + } + } + pub(crate) fn is_client_error(&self) -> bool { // Failing validation or imagemagick bailing probably means bad input matches!( diff --git a/src/process.rs b/src/process.rs index 8a9667b..9621444 100644 --- a/src/process.rs +++ b/src/process.rs @@ -14,6 +14,8 @@ use tokio::{ }; use tracing::{Instrument, Span}; +use crate::error_code::ErrorCode; + struct MetricsGuard { start: Instant, armed: bool, @@ -100,6 +102,18 @@ pub(crate) enum ProcessError { Other(#[source] std::io::Error), } +impl ProcessError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::NotFound(_) => ErrorCode::COMMAND_NOT_FOUND, + Self::PermissionDenied(_) => ErrorCode::COMMAND_PERMISSION_DENIED, + Self::LimitReached | Self::Other(_) => ErrorCode::COMMAND_ERROR, + Self::Timeout(_) => ErrorCode::COMMAND_TIMEOUT, + Self::Status(_, _) => ErrorCode::COMMAND_FAILURE, + } + } +} + impl Process { pub(crate) fn run(command: &str, args: &[&str], timeout: u64) -> Result { let res = tracing::trace_span!(parent: None, "Create command", %command) diff --git a/src/queue.rs b/src/queue.rs index f6f8037..8a3fc20 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,7 +1,7 @@ use crate::{ concurrent_processor::ProcessMap, config::Configuration, - error::Error, + error::{Error, UploadError}, formats::InputProcessableFormat, repo::{Alias, DeleteToken, FullRepo, Hash, JobId, UploadId}, serde_str::Serde, @@ -94,13 +94,14 @@ pub(crate) async fn cleanup_alias( let job = serde_json::to_vec(&Cleanup::Alias { alias: Serde::new(alias), token: Serde::new(token), - })?; + }) + .map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } pub(crate) async fn cleanup_hash(repo: &Arc, hash: Hash) -> Result<(), Error> { - let job = serde_json::to_vec(&Cleanup::Hash { hash })?; + let job = serde_json::to_vec(&Cleanup::Hash { hash }).map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } @@ -111,7 +112,8 @@ pub(crate) async fn cleanup_identifier( ) -> Result<(), Error> { let job = serde_json::to_vec(&Cleanup::Identifier { identifier: Base64Bytes(identifier.to_bytes()?), - })?; + }) + .map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } @@ -121,25 +123,26 @@ async fn cleanup_variants( hash: Hash, variant: Option, ) -> Result<(), Error> { - let job = serde_json::to_vec(&Cleanup::Variant { hash, variant })?; + let job = + serde_json::to_vec(&Cleanup::Variant { hash, variant }).map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } pub(crate) async fn cleanup_outdated_proxies(repo: &Arc) -> Result<(), Error> { - let job = serde_json::to_vec(&Cleanup::OutdatedProxies)?; + let job = serde_json::to_vec(&Cleanup::OutdatedProxies).map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } pub(crate) async fn cleanup_outdated_variants(repo: &Arc) -> Result<(), Error> { - let job = serde_json::to_vec(&Cleanup::OutdatedVariants)?; + let job = serde_json::to_vec(&Cleanup::OutdatedVariants).map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } pub(crate) async fn cleanup_all_variants(repo: &Arc) -> Result<(), Error> { - let job = serde_json::to_vec(&Cleanup::AllVariants)?; + let job = serde_json::to_vec(&Cleanup::AllVariants).map_err(UploadError::PushJob)?; repo.push(CLEANUP_QUEUE, job.into()).await?; Ok(()) } @@ -154,7 +157,8 @@ pub(crate) async fn queue_ingest( identifier: Base64Bytes(identifier), declared_alias: declared_alias.map(Serde::new), upload_id: Serde::new(upload_id), - })?; + }) + .map_err(UploadError::PushJob)?; repo.push(PROCESS_QUEUE, job.into()).await?; Ok(()) } @@ -171,7 +175,8 @@ pub(crate) async fn queue_generate( source: Serde::new(source), process_path, process_args, - })?; + }) + .map_err(UploadError::PushJob)?; repo.push(PROCESS_QUEUE, job.into()).await?; Ok(()) } diff --git a/src/queue/process.rs b/src/queue/process.rs index 03e4641..39a1fb9 100644 --- a/src/queue/process.rs +++ b/src/queue/process.rs @@ -117,6 +117,7 @@ where UploadResult::Failure { message: e.root_cause().to_string(), + code: e.error_code().into_owned(), } } }; diff --git a/src/repo.rs b/src/repo.rs index 2f1bcf1..2518cb3 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1,6 +1,7 @@ use crate::{ config, details::Details, + error_code::{ErrorCode, OwnedErrorCode}, store::{Identifier, StoreError}, stream::LocalBoxStream, }; @@ -52,9 +53,16 @@ pub(crate) struct UploadId { id: Uuid, } +#[derive(Debug)] pub(crate) enum UploadResult { - Success { alias: Alias, token: DeleteToken }, - Failure { message: String }, + Success { + alias: Alias, + token: DeleteToken, + }, + Failure { + message: String, + code: OwnedErrorCode, + }, } #[derive(Debug, thiserror::Error)] @@ -69,6 +77,16 @@ pub(crate) enum RepoError { Canceled, } +impl RepoError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::SledError(e) => e.error_code(), + Self::AlreadyClaimed => ErrorCode::ALREADY_CLAIMED, + Self::Canceled => ErrorCode::PANIC, + } + } +} + #[async_trait::async_trait(?Send)] pub(crate) trait FullRepo: UploadRepo diff --git a/src/repo/sled.rs b/src/repo/sled.rs index 244e6a7..57527c2 100644 --- a/src/repo/sled.rs +++ b/src/repo/sled.rs @@ -1,5 +1,6 @@ use crate::{ details::HumanDate, + error_code::{ErrorCode, OwnedErrorCode}, serde_str::Serde, store::StoreError, stream::{from_iterator, LocalBoxStream}, @@ -46,7 +47,10 @@ pub(crate) enum SledError { Sled(#[from] sled::Error), #[error("Invalid details json")] - Details(#[from] serde_json::Error), + Details(serde_json::Error), + + #[error("Invalid upload result json")] + UploadResult(serde_json::Error), #[error("Error parsing variant key")] VariantKey(#[from] VariantKeyError), @@ -58,6 +62,18 @@ pub(crate) enum SledError { Conflict, } +impl SledError { + pub(super) const fn error_code(&self) -> ErrorCode { + match self { + Self::Sled(_) | Self::VariantKey(_) => ErrorCode::SLED_ERROR, + Self::Details(_) => ErrorCode::EXTRACT_DETAILS, + Self::UploadResult(_) => ErrorCode::EXTRACT_UPLOAD_RESULT, + Self::Panic => ErrorCode::PANIC, + Self::Conflict => ErrorCode::CONFLICTED_RECORD, + } + } +} + #[derive(Clone)] pub(crate) struct SledRepo { healthz_count: Arc, @@ -442,6 +458,7 @@ enum InnerUploadResult { }, Failure { message: String, + code: OwnedErrorCode, }, } @@ -452,7 +469,7 @@ impl From for InnerUploadResult { alias: Serde::new(alias), token: Serde::new(token), }, - UploadResult::Failure { message } => InnerUploadResult::Failure { message }, + UploadResult::Failure { message, code } => InnerUploadResult::Failure { message, code }, } } } @@ -464,7 +481,7 @@ impl From for UploadResult { alias: Serde::into_inner(alias), token: Serde::into_inner(token), }, - InnerUploadResult::Failure { message } => UploadResult::Failure { message }, + InnerUploadResult::Failure { message, code } => UploadResult::Failure { message, code }, } } } @@ -538,7 +555,7 @@ impl UploadRepo for SledRepo { if let Some(bytes) = opt { if bytes != b"1" { let result: InnerUploadResult = - serde_json::from_slice(&bytes).map_err(SledError::from)?; + serde_json::from_slice(&bytes).map_err(SledError::UploadResult)?; return Ok(result.into()); } } else { @@ -553,7 +570,7 @@ impl UploadRepo for SledRepo { sled::Event::Insert { value, .. } => { if value != b"1" { let result: InnerUploadResult = - serde_json::from_slice(&value).map_err(SledError::from)?; + serde_json::from_slice(&value).map_err(SledError::UploadResult)?; return Ok(result.into()); } } @@ -576,7 +593,7 @@ impl UploadRepo for SledRepo { result: UploadResult, ) -> Result<(), RepoError> { let result: InnerUploadResult = result.into(); - let result = serde_json::to_vec(&result).map_err(SledError::from)?; + let result = serde_json::to_vec(&result).map_err(SledError::UploadResult)?; b!(self.uploads, uploads.insert(upload_id.as_bytes(), result)); @@ -940,7 +957,7 @@ impl DetailsRepo for SledRepo { ) -> Result<(), StoreError> { let key = identifier.to_bytes()?; let details = serde_json::to_vec(&details.inner) - .map_err(SledError::from) + .map_err(SledError::Details) .map_err(RepoError::from)?; b!( @@ -959,7 +976,7 @@ impl DetailsRepo for SledRepo { opt.map(|ivec| serde_json::from_slice(&ivec).map(|inner| Details { inner })) .transpose() - .map_err(SledError::from) + .map_err(SledError::Details) .map_err(RepoError::from) .map_err(StoreError::from) } diff --git a/src/store.rs b/src/store.rs index 248138e..66c9337 100644 --- a/src/store.rs +++ b/src/store.rs @@ -4,6 +4,8 @@ use futures_core::Stream; use std::{fmt::Debug, sync::Arc}; use tokio::io::{AsyncRead, AsyncWrite}; +use crate::error_code::ErrorCode; + pub(crate) mod file_store; pub(crate) mod object_store; @@ -29,6 +31,15 @@ pub(crate) enum StoreError { } impl StoreError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::FileStore(e) => e.error_code(), + Self::ObjectStore(e) => e.error_code(), + Self::Repo(e) => e.error_code(), + Self::Repo04(_) => ErrorCode::OLD_REPO_ERROR, + Self::FileNotFound(_) | Self::ObjectNotFound(_) => ErrorCode::NOT_FOUND, + } + } pub(crate) const fn is_not_found(&self) -> bool { matches!(self, Self::FileNotFound(_)) || matches!(self, Self::ObjectNotFound(_)) } diff --git a/src/store/file_store.rs b/src/store/file_store.rs index 860fb52..b2a1cb7 100644 --- a/src/store/file_store.rs +++ b/src/store/file_store.rs @@ -1,4 +1,5 @@ use crate::{ + error_code::ErrorCode, file::File, repo::{Repo, SettingsRepo}, store::Store, @@ -32,16 +33,27 @@ pub(crate) enum FileError { #[error("Failed to generate path")] PathGenerator(#[from] storage_path_generator::PathError), - #[error("Error formatting file store identifier")] + #[error("Error formatting file store ID")] IdError, - #[error("Mailformed file store identifier")] + #[error("Malformed file store ID")] PrefixError, #[error("Tried to save over existing file")] FileExists, } +impl FileError { + pub(super) const fn error_code(&self) -> ErrorCode { + match self { + Self::Io(_) => ErrorCode::FILE_IO_ERROR, + Self::PathGenerator(_) => ErrorCode::PARSE_PATH_ERROR, + Self::FileExists => ErrorCode::FILE_EXISTS, + Self::IdError | Self::PrefixError => ErrorCode::FORMAT_FILE_ID_ERROR, + } + } +} + #[derive(Clone)] pub(crate) struct FileStore { path_gen: Generator, diff --git a/src/store/object_store.rs b/src/store/object_store.rs index f3767ec..a37f465 100644 --- a/src/store/object_store.rs +++ b/src/store/object_store.rs @@ -1,5 +1,6 @@ use crate::{ bytes_stream::BytesStream, + error_code::ErrorCode, repo::{Repo, SettingsRepo}, store::Store, stream::{IntoStreamer, StreamMap}, @@ -67,21 +68,39 @@ pub(crate) enum ObjectError { Etag, #[error("Task cancelled")] - Cancelled, + Canceled, #[error("Invalid status: {0}\n{1}")] Status(StatusCode, String), } +impl ObjectError { + pub(super) const fn error_code(&self) -> ErrorCode { + match self { + Self::PathGenerator(_) => ErrorCode::PARSE_PATH_ERROR, + Self::S3(_) + | Self::RequestMiddleware(_) + | Self::Request(_) + | Self::Xml(_) + | Self::Length + | Self::Etag + | Self::Status(_, _) => ErrorCode::OBJECT_REQUEST_ERROR, + Self::IO(_) => ErrorCode::OBJECT_IO_ERROR, + Self::Utf8(_) => ErrorCode::PARSE_OBJECT_ID_ERROR, + Self::Canceled => ErrorCode::PANIC, + } + } +} + impl From for ObjectError { fn from(_: JoinError) -> Self { - Self::Cancelled + Self::Canceled } } impl From for ObjectError { fn from(_: BlockingError) -> Self { - Self::Cancelled + Self::Canceled } } diff --git a/src/validate.rs b/src/validate.rs index 2342254..0d94c55 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -6,6 +6,7 @@ use crate::{ discover::Discovery, either::Either, error::Error, + error_code::ErrorCode, formats::{ AnimationFormat, AnimationOutput, ImageInput, ImageOutput, InputFile, InputVideoFormat, InternalFormat, Validations, @@ -38,6 +39,20 @@ pub(crate) enum ValidationError { VideoDisabled, } +impl ValidationError { + pub(crate) const fn error_code(&self) -> ErrorCode { + match self { + Self::Width => ErrorCode::VALIDATE_WIDTH, + Self::Height => ErrorCode::VALIDATE_HEIGHT, + Self::Area => ErrorCode::VALIDATE_AREA, + Self::Frames => ErrorCode::VALIDATE_FRAMES, + Self::Empty => ErrorCode::VALIDATE_FILE_EMPTY, + Self::Filesize => ErrorCode::VALIDATE_FILE_SIZE, + Self::VideoDisabled => ErrorCode::VIDEO_DISABLED, + } + } +} + const MEGABYTES: usize = 1024 * 1024; #[tracing::instrument(skip_all)]