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
92impl Error {
93    /// Creates an [`Error::Panic`] from a panic payload, extracting the message.
94    pub fn panic(payload: Box<dyn Any + Send>) -> Self {
95        let msg = if let Some(s) = payload.downcast_ref::<&str>() {
96            (*s).to_owned()
97        } else if let Some(s) = payload.downcast_ref::<String>() {
98            s.clone()
99        } else {
100            "unknown panic".to_owned()
101        };
102        Self::Panic(msg)
103    }
104
105    /// Creates an [`Error::Reqwest`] from a reqwest error with context.
106    pub fn reqwest(context: impl Into<String>, cause: reqwest::Error) -> Self {
107        Self::Reqwest {
108            context: context.into(),
109            cause,
110        }
111    }
112
113    /// Creates an [`Error::Serde`] from a serde error with context.
114    pub fn serde(context: impl Into<String>, cause: serde_json::Error) -> Self {
115        Self::Serde {
116            context: context.into(),
117            cause,
118        }
119    }
120
121    /// Creates an [`Error::Generic`] with a context string and no cause.
122    pub fn generic(context: impl Into<String>) -> Self {
123        Self::Generic {
124            context: context.into(),
125            cause: None,
126        }
127    }
128
129    /// Returns the appropriate log level for this error.
130    pub fn level(&self) -> Level {
131        match self {
132            // Malformed client input at DEBUG level
133            Self::Client(_) => Level::DEBUG,
134            Self::Metadata(_) => Level::DEBUG,
135            // Like rate limits, we treat capacity errors as warnings
136            Self::AtCapacity => Level::WARN,
137            // All other errors are service or backend failures
138            Self::Io(_) => Level::ERROR,
139            Self::Serde { .. } => Level::ERROR,
140            Self::Reqwest { .. } => Level::ERROR,
141            Self::GcpAuth(_) => Level::ERROR,
142            Self::Panic(_) => Level::ERROR,
143            Self::Dropped => Level::ERROR,
144            Self::UnexpectedTombstone => Level::ERROR,
145            Self::Generic { .. } => Level::ERROR,
146        }
147    }
148}
149
150/// Result type for service operations.
151pub type Result<T, E = Error> = std::result::Result<T, E>;