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