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 requested byte range is not satisfiable for the object's size.
77 #[error("range not satisfiable (object size: {total} bytes)")]
78 RangeNotSatisfiable {
79 /// Total size of the object in bytes.
80 total: u64,
81 },
82
83 /// The service has reached its concurrency limit and cannot accept more operations.
84 #[error("concurrency limit reached")]
85 AtCapacity,
86
87 /// Any other error stemming from one of the storage backends, which might be specific to that
88 /// backend or to a certain operation.
89 #[error("storage backend error: {context}")]
90 Generic {
91 /// Context describing the operation that failed.
92 context: String,
93 /// The underlying error, if available.
94 #[source]
95 cause: Option<Box<dyn std::error::Error + Send + Sync>>,
96 },
97
98 /// The functionality is not implemented by this instance of the service.
99 #[error("not implemented")]
100 NotImplemented,
101
102 /// Invalid upload ID (e.g. path traversal attempt).
103 #[error(transparent)]
104 InvalidUploadId(#[from] objectstore_types::multipart::InvalidUploadId),
105}
106
107impl Error {
108 /// Creates an [`Error::Panic`] from a panic payload, extracting the message.
109 pub fn panic(payload: Box<dyn Any + Send>) -> Self {
110 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
111 (*s).to_owned()
112 } else if let Some(s) = payload.downcast_ref::<String>() {
113 s.clone()
114 } else {
115 "unknown panic".to_owned()
116 };
117 Self::Panic(msg)
118 }
119
120 /// Creates an [`Error::Reqwest`] from a reqwest error with context.
121 pub fn reqwest(context: impl Into<String>, cause: reqwest::Error) -> Self {
122 Self::Reqwest {
123 context: context.into(),
124 cause,
125 }
126 }
127
128 /// Creates an [`Error::Serde`] from a serde error with context.
129 pub fn serde(context: impl Into<String>, cause: serde_json::Error) -> Self {
130 Self::Serde {
131 context: context.into(),
132 cause,
133 }
134 }
135
136 /// Creates an [`Error::Generic`] with a context string and no cause.
137 pub fn generic(context: impl Into<String>) -> Self {
138 Self::Generic {
139 context: context.into(),
140 cause: None,
141 }
142 }
143
144 /// Returns the appropriate log level for this error.
145 pub fn level(&self) -> Level {
146 match self {
147 // Malformed client input at DEBUG level
148 Self::Client(_) => Level::DEBUG,
149 Self::Metadata(_) => Level::DEBUG,
150 Self::RangeNotSatisfiable { .. } => Level::DEBUG,
151 // Like rate limits, we treat capacity errors as warnings
152 Self::AtCapacity => Level::WARN,
153 // All other errors are service or backend failures
154 Self::Io(_) => Level::ERROR,
155 Self::Serde { .. } => Level::ERROR,
156 Self::Reqwest { .. } => Level::ERROR,
157 Self::GcpAuth(_) => Level::ERROR,
158 Self::Panic(_) => Level::ERROR,
159 Self::Dropped => Level::ERROR,
160 Self::UnexpectedTombstone => Level::ERROR,
161 Self::NotImplemented => Level::ERROR,
162 Self::InvalidUploadId(_) => Level::DEBUG,
163 Self::Generic { .. } => Level::ERROR,
164 }
165 }
166}
167
168/// Result type for service operations.
169pub type Result<T, E = Error> = std::result::Result<T, E>;