1use std::any::Any;
16
17use common_error::ext::{ErrorExt, RetryHint};
18use common_error::status_code::StatusCode;
19use common_macro::stack_trace_debug;
20use object_store::error::retry_hint_from_opendal_error;
21use snafu::{Location, Snafu};
22
23#[derive(Snafu)]
24#[snafu(visibility(pub))]
25#[stack_trace_debug]
26pub enum Error {
27 #[snafu(display("Invalid URI '{}': {}", uri, reason))]
28 InvalidUri {
29 uri: String,
30 reason: String,
31 #[snafu(implicit)]
32 location: Location,
33 },
34
35 #[snafu(display("Unsupported storage scheme: {}", scheme))]
36 UnsupportedScheme {
37 scheme: String,
38 #[snafu(implicit)]
39 location: Location,
40 },
41
42 #[snafu(display("Storage operation '{}' failed", operation))]
43 StorageOperation {
44 operation: String,
45 #[snafu(source)]
46 error: object_store::Error,
47 #[snafu(implicit)]
48 location: Location,
49 },
50
51 #[snafu(display("Failed to parse manifest"))]
52 ManifestParse {
53 #[snafu(source)]
54 error: serde_json::Error,
55 #[snafu(implicit)]
56 location: Location,
57 },
58
59 #[snafu(display("Failed to serialize manifest"))]
60 ManifestSerialize {
61 #[snafu(source)]
62 error: serde_json::Error,
63 #[snafu(implicit)]
64 location: Location,
65 },
66
67 #[snafu(display("Failed to decode text file as UTF-8"))]
68 TextDecode {
69 #[snafu(source)]
70 error: std::string::FromUtf8Error,
71 #[snafu(implicit)]
72 location: Location,
73 },
74
75 #[snafu(display("I/O error while {}: {}", operation, error))]
76 Io {
77 operation: &'static str,
78 error: std::io::Error,
79 #[snafu(implicit)]
80 location: Location,
81 },
82
83 #[snafu(display(
84 "Cannot resume snapshot with a different schema_only mode (existing: {}, requested: {}). Use --force to recreate.",
85 existing_schema_only,
86 requested_schema_only
87 ))]
88 SchemaOnlyModeMismatch {
89 existing_schema_only: bool,
90 requested_schema_only: bool,
91 #[snafu(implicit)]
92 location: Location,
93 },
94
95 #[snafu(display(
96 "Cannot resume snapshot with different {} (existing: {}, requested: {}). Use --force to recreate.",
97 field,
98 existing,
99 requested
100 ))]
101 ResumeConfigMismatch {
102 field: String,
103 existing: String,
104 requested: String,
105 #[snafu(implicit)]
106 location: Location,
107 },
108
109 #[snafu(display("Failed to parse time: invalid format: {}", input))]
110 TimeParseInvalidFormat {
111 input: String,
112 #[snafu(implicit)]
113 location: Location,
114 },
115
116 #[snafu(display("Failed to parse time: end_time is before start_time"))]
117 TimeParseEndBeforeStart {
118 #[snafu(implicit)]
119 location: Location,
120 },
121
122 #[snafu(display(
123 "chunk_time_window requires both --start-time and --end-time to be specified"
124 ))]
125 ChunkTimeWindowRequiresBounds {
126 #[snafu(implicit)]
127 location: Location,
128 },
129
130 #[snafu(display("--schema-only cannot be used with data export arguments: {}", args))]
131 SchemaOnlyArgsNotAllowed {
132 args: String,
133 #[snafu(implicit)]
134 location: Location,
135 },
136
137 #[snafu(display("Empty result from query"))]
138 EmptyResult {
139 #[snafu(implicit)]
140 location: Location,
141 },
142
143 #[snafu(display("Unexpected value type in query result"))]
144 UnexpectedValueType {
145 #[snafu(implicit)]
146 location: Location,
147 },
148
149 #[snafu(display("Database error"))]
150 Database {
151 #[snafu(source)]
152 error: crate::error::Error,
153 #[snafu(implicit)]
154 location: Location,
155 },
156
157 #[snafu(display("Snapshot not found at '{}'", uri))]
158 SnapshotNotFound {
159 uri: String,
160 #[snafu(implicit)]
161 location: Location,
162 },
163
164 #[snafu(display("Schema '{}' not found in catalog '{}'", schema, catalog))]
165 SchemaNotFound {
166 catalog: String,
167 schema: String,
168 #[snafu(implicit)]
169 location: Location,
170 },
171
172 #[snafu(display("Failed to parse URL"))]
173 UrlParse {
174 #[snafu(source)]
175 error: url::ParseError,
176 #[snafu(implicit)]
177 location: Location,
178 },
179
180 #[snafu(display("Failed to build object store"))]
181 BuildObjectStore {
182 #[snafu(source)]
183 error: object_store::Error,
184 #[snafu(implicit)]
185 location: Location,
186 },
187
188 #[snafu(display("Manifest version mismatch: expected {}, found {}", expected, found))]
189 ManifestVersionMismatch {
190 expected: u32,
191 found: u32,
192 #[snafu(implicit)]
193 location: Location,
194 },
195
196 #[snafu(display(
197 "Snapshot verification failed: {} error(s), {} warning(s)",
198 errors,
199 warnings
200 ))]
201 SnapshotVerifyFailed {
202 errors: usize,
203 warnings: usize,
204 #[snafu(implicit)]
205 location: Location,
206 },
207}
208
209pub type Result<T> = std::result::Result<T, Error>;
210
211impl ErrorExt for Error {
212 fn status_code(&self) -> StatusCode {
213 match self {
214 Error::InvalidUri { .. }
215 | Error::UnsupportedScheme { .. }
216 | Error::SchemaOnlyModeMismatch { .. }
217 | Error::ResumeConfigMismatch { .. }
218 | Error::ManifestVersionMismatch { .. }
219 | Error::SchemaOnlyArgsNotAllowed { .. }
220 | Error::SnapshotVerifyFailed { .. } => StatusCode::InvalidArguments,
221 Error::TimeParseInvalidFormat { .. }
222 | Error::TimeParseEndBeforeStart { .. }
223 | Error::ChunkTimeWindowRequiresBounds { .. } => StatusCode::InvalidArguments,
224
225 Error::StorageOperation { .. }
226 | Error::ManifestParse { .. }
227 | Error::ManifestSerialize { .. }
228 | Error::TextDecode { .. }
229 | Error::BuildObjectStore { .. } => StatusCode::StorageUnavailable,
230
231 Error::EmptyResult { .. }
232 | Error::UnexpectedValueType { .. }
233 | Error::UrlParse { .. } => StatusCode::Internal,
234
235 Error::Io { .. } => StatusCode::External,
236
237 Error::Database { error, .. } => error.status_code(),
238
239 Error::SnapshotNotFound { .. } => StatusCode::InvalidArguments,
240 Error::SchemaNotFound { .. } => StatusCode::DatabaseNotFound,
241 }
242 }
243
244 fn as_any(&self) -> &dyn Any {
245 self
246 }
247
248 fn retry_hint(&self) -> RetryHint {
249 match self {
250 Error::StorageOperation { error, .. } | Error::BuildObjectStore { error, .. } => {
251 retry_hint_from_opendal_error(error)
252 }
253 Error::Database { error, .. } => error.retry_hint(),
254 _ => RetryHint::NonRetryable,
255 }
256 }
257}