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>;