Skip to main content

objectstore_service/
error.rs

1//! Error types for service and backend operations.
2//!
3//! [`Error`] covers I/O, serialization, HTTP, metadata, authentication,
4//! and backend-specific failures. [`Result`] is the corresponding alias.
5
6use std::any::Any;
7
8use objectstore_log::Level;
9use thiserror::Error as ThisError;
10
11use crate::stream::ClientError;
12
13/// Error type for service operations.
14#[derive(Debug, ThisError)]
15pub enum Error {
16    /// IO errors related to payload streaming or file operations.
17    #[error("i/o error: {0}")]
18    Io(#[from] std::io::Error),
19
20    /// Error originating from a client-supplied input stream.
21    ///
22    /// Indicates the client is at fault (e.g. dropped connection mid-upload) and should
23    /// map to a 4xx response rather than a 5xx.
24    #[error("error reading client stream: {0}")]
25    Client(#[from] ClientError),
26
27    /// Errors related to de/serialization.
28    #[error("serde error: {context}")]
29    Serde {
30        /// Context describing what was being serialized/deserialized.
31        context: String,
32        /// The underlying serde error.
33        #[source]
34        cause: serde_json::Error,
35    },
36
37    /// All errors stemming from the reqwest client, used in multiple backends to send requests to
38    /// e.g. GCP APIs.
39    /// These can be network errors encountered when sending the requests, but can also indicate
40    /// errors returned by the API itself.
41    #[error("reqwest error: {context}")]
42    Reqwest {
43        /// Context describing the request that failed.
44        context: String,
45        /// The underlying reqwest error.
46        #[source]
47        cause: reqwest::Error,
48    },
49
50    /// Errors related to de/serialization and parsing of object metadata.
51    #[error("metadata error: {0}")]
52    Metadata(#[from] objectstore_types::metadata::Error),
53
54    /// Errors encountered when attempting to authenticate with GCP.
55    #[error("GCP authentication error: {0}")]
56    GcpAuth(#[from] gcp_auth::Error),
57
58    /// A spawned service task panicked.
59    #[error("service task failed: {0}")]
60    Panic(String),
61
62    /// A spawned service task was dropped before it could deliver its result.
63    ///
64    /// This is an unexpected condition that can occur when the runtime drops the task for unknown
65    /// reasons.
66    #[error("task dropped")]
67    Dropped,
68
69    /// A redirect tombstone was encountered at a place where it is not supported.
70    ///
71    /// This indicates a caller bug — tombstone-aware reads must go through the
72    /// [`HighVolumeBackend`](crate::backend::common::HighVolumeBackend) methods.
73    #[error("unexpected tombstone")]
74    UnexpectedTombstone,
75
76    /// The service has reached its concurrency limit and cannot accept more operations.
77    #[error("concurrency limit reached")]
78    AtCapacity,
79
80    /// Any other error stemming from one of the storage backends, which might be specific to that
81    /// backend or to a certain operation.
82    #[error("storage backend error: {context}")]
83    Generic {
84        /// Context describing the operation that failed.
85        context: String,
86        /// The underlying error, if available.
87        #[source]
88        cause: Option<Box<dyn std::error::Error + Send + Sync>>,
89    },
90
91    /// The functionality is not implemented by this instance of the service.
92    #[error("not implemented")]
93    NotImplemented,
94
95    /// Invalid upload ID (e.g. path traversal attempt).
96    #[error(transparent)]
97    InvalidUploadId(#[from] objectstore_types::multipart::InvalidUploadId),
98}
99
100impl Error {
101    /// Creates an [`Error::Panic`] from a panic payload, extracting the message.
102    pub fn panic(payload: Box<dyn Any + Send>) -> Self {
103        let msg = if let Some(s) = payload.downcast_ref::<&str>() {
104            (*s).to_owned()
105        } else if let Some(s) = payload.downcast_ref::<String>() {
106            s.clone()
107        } else {
108            "unknown panic".to_owned()
109        };
110        Self::Panic(msg)
111    }
112
113    /// Creates an [`Error::Reqwest`] from a reqwest error with context.
114    pub fn reqwest(context: impl Into<String>, cause: reqwest::Error) -> Self {
115        Self::Reqwest {
116            context: context.into(),
117            cause,
118        }
119    }
120
121    /// Creates an [`Error::Serde`] from a serde error with context.
122    pub fn serde(context: impl Into<String>, cause: serde_json::Error) -> Self {
123        Self::Serde {
124            context: context.into(),
125            cause,
126        }
127    }
128
129    /// Creates an [`Error::Generic`] with a context string and no cause.
130    pub fn generic(context: impl Into<String>) -> Self {
131        Self::Generic {
132            context: context.into(),
133            cause: None,
134        }
135    }
136
137    /// Returns the appropriate log level for this error.
138    pub fn level(&self) -> Level {
139        match self {
140            // Malformed client input at DEBUG level
141            Self::Client(_) => Level::DEBUG,
142            Self::Metadata(_) => Level::DEBUG,
143            // Like rate limits, we treat capacity errors as warnings
144            Self::AtCapacity => Level::WARN,
145            // All other errors are service or backend failures
146            Self::Io(_) => Level::ERROR,
147            Self::Serde { .. } => Level::ERROR,
148            Self::Reqwest { .. } => Level::ERROR,
149            Self::GcpAuth(_) => Level::ERROR,
150            Self::Panic(_) => Level::ERROR,
151            Self::Dropped => Level::ERROR,
152            Self::UnexpectedTombstone => Level::ERROR,
153            Self::NotImplemented => Level::ERROR,
154            Self::InvalidUploadId(_) => Level::DEBUG,
155            Self::Generic { .. } => Level::ERROR,
156        }
157    }
158}
159
160/// Result type for service operations.
161pub type Result<T, E = Error> = std::result::Result<T, E>;