1use std::any::Any;
16
17use common_error::ext::{ErrorExt, RetryHint, retry_hint_from_io_error};
18use common_error::status_code::StatusCode;
19use common_macro::stack_trace_debug;
20use snafu::{Location, Snafu};
21
22use crate::data::export_v2::manifest::ChunkStatus;
23
24#[derive(Snafu)]
25#[snafu(visibility(pub))]
26#[stack_trace_debug]
27pub enum Error {
28 #[snafu(display("Invalid packed snapshot: {reason}"))]
29 InvalidPackedSnapshot { reason: String },
30
31 #[snafu(display("Snapshot not found at '{}'", uri))]
32 SnapshotNotFound {
33 uri: String,
34 #[snafu(implicit)]
35 location: Location,
36 },
37
38 #[snafu(display("Schema '{}' not found in snapshot", schema))]
39 SchemaNotInSnapshot {
40 schema: String,
41 #[snafu(implicit)]
42 location: Location,
43 },
44
45 #[snafu(display("Incomplete snapshot: chunk {} has status {:?}", chunk_id, status))]
46 IncompleteSnapshot {
47 chunk_id: u32,
48 status: ChunkStatus,
49 #[snafu(implicit)]
50 location: Location,
51 },
52
53 #[snafu(display(
54 "Snapshot is inconsistent: chunk {} is marked completed but its file manifest is empty",
55 chunk_id
56 ))]
57 EmptyChunkManifest {
58 chunk_id: u32,
59 #[snafu(implicit)]
60 location: Location,
61 },
62
63 #[snafu(display(
64 "Snapshot is inconsistent: chunk {} for schema '{}' is marked completed but no files were found under '{}'",
65 chunk_id,
66 schema,
67 path
68 ))]
69 MissingChunkData {
70 chunk_id: u32,
71 schema: String,
72 path: String,
73 #[snafu(implicit)]
74 location: Location,
75 },
76
77 #[snafu(display("Chunk {} import failed for schema '{}'", chunk_id, schema))]
78 ChunkImportFailed {
79 chunk_id: u32,
80 schema: String,
81 #[snafu(source)]
82 error: crate::data::export_v2::error::Error,
83 #[snafu(implicit)]
84 location: Location,
85 },
86
87 #[snafu(display("Snapshot storage error"))]
88 SnapshotStorage {
89 #[snafu(source)]
90 error: crate::data::export_v2::error::Error,
91 #[snafu(implicit)]
92 location: Location,
93 },
94
95 #[snafu(display("Database error"))]
96 Database {
97 #[snafu(source)]
98 error: crate::error::Error,
99 #[snafu(implicit)]
100 location: Location,
101 },
102
103 #[snafu(display("Failed to parse import state file"))]
104 ImportStateParse {
105 #[snafu(source)]
106 error: serde_json::Error,
107 #[snafu(implicit)]
108 location: Location,
109 },
110
111 #[snafu(display("Import state I/O failed at '{}': {}", path, error))]
112 ImportStateIo {
113 path: String,
114 #[snafu(source)]
115 error: std::io::Error,
116 #[snafu(implicit)]
117 location: Location,
118 },
119
120 #[snafu(display("Import state is already locked at '{}'", path))]
121 ImportStateLocked {
122 path: String,
123 #[snafu(implicit)]
124 location: Location,
125 },
126
127 #[snafu(display(
128 "Failed to determine import state path for snapshot '{}'. Set HOME, USERPROFILE, or run from a valid current directory.",
129 snapshot_id
130 ))]
131 ImportStatePathUnavailable {
132 snapshot_id: String,
133 #[snafu(implicit)]
134 location: Location,
135 },
136
137 #[snafu(display(
138 "Import state at '{}' does not match current import: {}. Either rerun with matching import arguments, or delete the state file to start over (DDL will be re-executed).",
139 path,
140 reason
141 ))]
142 ImportStateMismatch {
143 path: String,
144 reason: String,
145 #[snafu(implicit)]
146 location: Location,
147 },
148
149 #[cfg(test)]
150 #[snafu(display("Test task failed: {}", message))]
151 TestTaskFailed {
152 message: String,
153 retryable: bool,
154 #[snafu(implicit)]
155 location: Location,
156 },
157
158 #[snafu(display(
159 "Import state references unknown task: chunk {}, schema '{}'",
160 chunk_id,
161 schema
162 ))]
163 ImportStateUnknownTask {
164 chunk_id: u32,
165 schema: String,
166 #[snafu(implicit)]
167 location: Location,
168 },
169
170 #[snafu(display(
171 "Import state at '{}' is not ready for data import: DDL has not been marked completed",
172 path
173 ))]
174 ImportStateDdlIncomplete {
175 path: String,
176 #[snafu(implicit)]
177 location: Location,
178 },
179}
180
181pub type Result<T> = std::result::Result<T, Error>;
182
183impl ErrorExt for Error {
184 fn status_code(&self) -> StatusCode {
185 match self {
186 Error::SnapshotNotFound { .. }
187 | Error::SchemaNotInSnapshot { .. }
188 | Error::IncompleteSnapshot { .. }
189 | Error::EmptyChunkManifest { .. }
190 | Error::InvalidPackedSnapshot { .. }
191 | Error::MissingChunkData { .. } => StatusCode::InvalidArguments,
192 Error::ImportStatePathUnavailable { .. }
193 | Error::ImportStateUnknownTask { .. }
194 | Error::ImportStateDdlIncomplete { .. } => StatusCode::Unexpected,
195 Error::ImportStateMismatch { .. } => StatusCode::InvalidArguments,
196 #[cfg(test)]
197 Error::TestTaskFailed { retryable, .. } => {
198 if *retryable {
199 StatusCode::StorageUnavailable
200 } else {
201 StatusCode::InvalidArguments
202 }
203 }
204 Error::Database { error, .. } => error.status_code(),
205 Error::SnapshotStorage { error, .. } | Error::ChunkImportFailed { error, .. } => {
206 error.status_code()
207 }
208 Error::ImportStateParse { .. } => StatusCode::Internal,
209 Error::ImportStateIo { .. } => StatusCode::StorageUnavailable,
210 Error::ImportStateLocked { .. } => StatusCode::IllegalState,
211 }
212 }
213
214 fn retry_hint(&self) -> RetryHint {
215 match self {
216 #[cfg(test)]
217 Error::TestTaskFailed { retryable, .. } => {
218 if *retryable {
219 RetryHint::Retryable
220 } else {
221 RetryHint::NonRetryable
222 }
223 }
224 Error::Database { error, .. } => error.retry_hint(),
225 Error::SnapshotStorage { error, .. } | Error::ChunkImportFailed { error, .. } => {
226 error.retry_hint()
227 }
228 Error::ImportStateIo { error, .. } => retry_hint_from_io_error(error),
229 _ => RetryHint::NonRetryable,
230 }
231 }
232
233 fn as_any(&self) -> &dyn Any {
234 self
235 }
236}