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