1use std::path::PathBuf;
2use std::path::StripPrefixError;
3use std::str::Utf8Error;
4
5use aws_smithy_types::byte_stream;
6use reqwest::header::ToStrError;
7use thiserror::Error;
8
9use crate::io::remote::HostChecksums;
10use crate::object_hash::Error as ObjectHashError;
11use crate::workflow::WorkflowValidationError;
12use quilt_uri::Host;
13use quilt_uri::Namespace;
14use quilt_uri::UriError;
15
16#[derive(Error, Debug)]
17#[error("S3 error{}: {kind}", .host.as_ref().map_or(String::new(), |h| format!(" for {h}")))]
18pub struct S3Error {
19 pub host: Option<Host>,
20 #[source]
21 pub kind: S3ErrorKind,
22}
23
24impl S3Error {
25 #[must_use]
26 pub fn new(kind: S3ErrorKind) -> Self {
27 Self { host: None, kind }
28 }
29
30 #[must_use]
31 pub fn is_not_found(&self) -> bool {
32 matches!(self.kind, S3ErrorKind::NotFound(_))
33 }
34
35 #[must_use]
39 pub fn is_access_denied(&self) -> bool {
40 matches!(self.kind, S3ErrorKind::AccessDenied(_))
41 }
42}
43
44#[derive(Error, Debug, PartialEq)]
45pub enum S3ErrorKind {
46 #[error("Failed to check object existence: {0}")]
47 Exists(String),
48
49 #[error("Failed to get object: {0}")]
50 GetObject(String),
51
52 #[error("Failed to get object attributes: {0}")]
53 GetObjectAttributes(String),
54
55 #[error("Failed to get object stream: {0}")]
56 GetObjectStream(String),
57
58 #[error("Failed to initialize S3 client: {0}")]
59 Client(String),
60
61 #[error("Failed to list objects: {0}")]
62 ListObjects(String),
63
64 #[error("Failed to put object: {0}")]
65 PutObject(String),
66
67 #[error("Failed to resolve object URL: {0}")]
68 ResolveUrl(String),
69
70 #[error("Failed to upload object: {0}")]
71 UploadFile(String),
72
73 #[error("S3 not found: {0}")]
74 NotFound(String),
75
76 #[error("S3 access denied: {0}")]
77 AccessDenied(String),
78
79 #[error("S3 error: {0}")]
80 Raw(String),
81
82 #[error("Failed to initialize S3 Remote")]
83 RemoteInit,
84
85 #[error("Object key expected to be present")]
86 ObjectKey,
87
88 #[error("Error with upload id: {0}")]
89 UploadId(String),
90
91 #[error("Failed to read RwLock: {0}")]
92 PoisonLock(String),
93}
94
95#[derive(Error, Debug, PartialEq)]
96pub enum AuthError {
97 #[error("Failed to read credentials: {0}")]
98 CredentialsRead(String),
99
100 #[error("Failed to refresh credentials: {0}")]
101 CredentialsRefresh(String),
102
103 #[error("Failed to read tokens: {0}")]
104 TokensRead(String),
105
106 #[error("Failed to refresh tokens: {0}")]
107 TokensRefresh(String),
108
109 #[error("Failed to exchange authorization code for tokens: {0}")]
110 TokensExchange(String),
111}
112
113#[derive(Error, Debug, PartialEq)]
114pub enum RoleError {
115 #[error("Not authenticated with {0}")]
116 NotAuthenticated(Host),
117
118 #[error("Registry rejected the request: {0}")]
119 GraphQl(String),
120
121 #[error("Role switch rejected: {0}")]
122 SwitchRejected(String),
123}
124
125#[derive(Error, Debug, PartialEq)]
126pub enum InstallPackageError {
127 #[error("The package {0} is already installed")]
128 AlreadyInstalled(Namespace),
129
130 #[error("The given package is not installed: {0}")]
131 NotInstalled(Namespace),
132}
133
134#[derive(Error, Debug, PartialEq)]
135pub enum InstallPathError {
136 #[error("Failed to install path: {}", .0.display())]
137 Install(PathBuf),
138
139 #[error("Some paths are already installed")]
140 AlreadyInstalled,
141
142 #[error("Failed to uninstall path: {}", .0.display())]
143 Uninstall(PathBuf),
144}
145
146#[derive(Error, Debug)]
147pub enum ChecksumError {
148 #[error("Checksum error: {0}")]
149 Mismatch(String),
150
151 #[error("Missing checksum: {0:?}")]
152 Missing(HostChecksums),
153
154 #[error("Malformed checksum: {0}")]
155 Malformed(String),
156
157 #[error("Failed to get checksum from S3: {0}")]
158 NoS3Checksum(String),
159}
160
161#[derive(Error, Debug)]
162pub enum ManifestError {
163 #[error("Manifest header: {0}")]
164 Header(String),
165
166 #[error("Failed to load manifest from {path}: {source}")]
167 Load {
168 path: PathBuf,
169 source: Box<crate::Error>,
170 },
171
172 #[error("Table error: {0}")]
173 Table(String),
174}
175
176#[derive(Error, Debug)]
177pub enum LineageError {
178 #[error("Domain lineage missing, including missing Home directory")]
179 Missing,
180
181 #[error("Domain lineage missing Home directory")]
182 MissingHome,
183
184 #[error("Failed to parse lineage file: {0}")]
185 Parse(serde_json::Error),
186
187 #[error("Operation requires a remote origin, but this is a local-only package")]
188 NoRemote,
189}
190
191#[derive(Error, Debug, PartialEq)]
192pub enum RemoteCatalogError {
193 #[error("Workflow error: {0}")]
194 Workflow(String),
195
196 #[error("Invalid workflows config: {0}")]
203 InvalidWorkflowsConfig(String),
204
205 #[error("Failed to fetch host config: {0}")]
206 HostConfig(String),
207
208 #[error("S3 bucket '{0}' is not reachable — verify the bucket name")]
209 BucketUnreachable(String),
210}
211
212#[derive(Error, Debug, PartialEq)]
213pub enum LoginError {
214 #[error("Login required{}", .0.as_ref().map_or(String::new(), |h| format!(": {h}")))]
215 Required(Option<Host>),
216
217 #[error("Failed to get registry URL from {0}. Does {0}/config.json have it?")]
218 RequiredRegistryUrl(Host),
219}
220
221#[derive(Error, Debug)]
222pub enum FsError {
223 #[error("Failed to read file {path}: {source}")]
224 Read {
225 path: PathBuf,
226 source: std::io::Error,
227 },
228
229 #[error("Failed to write file {path}: {source}")]
230 Write {
231 path: PathBuf,
232 source: std::io::Error,
233 },
234
235 #[error("Failed to copy file from {from} to {to}: {source}")]
236 Copy {
237 from: PathBuf,
238 to: PathBuf,
239 source: std::io::Error,
240 },
241
242 #[error("Failed to create directory {path}: {source}")]
243 DirectoryCreate {
244 path: PathBuf,
245 source: std::io::Error,
246 },
247
248 #[error("File not found: {path}")]
249 NotFound { path: PathBuf },
250
251 #[error("Path prefix not found: {0}")]
252 PathPrefixNotFound(StripPrefixError),
253
254 #[error("ByteStream error: {0}")]
255 ByteStream(#[from] byte_stream::error::Error),
256}
257
258#[derive(Error, Debug, PartialEq)]
259pub enum PackageOpError {
260 #[error("Commit error: {0}")]
261 Commit(String),
262
263 #[error("Push error: {0}")]
264 Push(String),
265
266 #[error("Publish error: {0}")]
267 Publish(String),
268
269 #[error("General error regarding package: {0}")]
270 Package(String),
271
272 #[error("Pull blocked by conflicting local changes: {0:?}")]
273 PullConflict(Vec<PathBuf>),
274
275 #[error("package is already up-to-date")]
276 AlreadyUpToDate,
277}
278
279#[derive(Error, Debug)]
281pub enum Error {
282 #[error("Authentication failed for {0}: {1}")]
283 Auth(Host, AuthError),
284
285 #[error(transparent)]
286 Checksum(#[from] ChecksumError),
287
288 #[error(transparent)]
289 Fs(#[from] FsError),
290
291 #[error(transparent)]
292 InstallPackage(InstallPackageError),
293
294 #[error(transparent)]
295 InstallPath(InstallPathError),
296
297 #[error("IO error: {0}")]
298 Io(#[from] std::io::Error),
299
300 #[error("JSON error: {0}")]
301 Json(#[from] serde_json::Error),
302
303 #[error(transparent)]
304 Lineage(#[from] LineageError),
305
306 #[error(transparent)]
307 Login(#[from] LoginError),
308
309 #[error(transparent)]
310 Manifest(#[from] ManifestError),
311
312 #[error(transparent)]
313 ObjectHash(#[from] ObjectHashError),
314
315 #[error(transparent)]
316 PackageOp(#[from] PackageOpError),
317
318 #[error("Reqwest error: {0}")]
319 Reqwest(#[from] reqwest::Error),
320
321 #[error(transparent)]
322 Role(#[from] RoleError),
323
324 #[error(transparent)]
325 RemoteCatalog(#[from] RemoteCatalogError),
326
327 #[error(transparent)]
328 S3(#[from] S3Error),
329
330 #[error("Cannot convert to string: {0}")]
331 ToString(#[from] ToStrError),
332
333 #[error("Integer conversion error: {0}")]
334 TryFromIntError(#[from] std::num::TryFromIntError),
335
336 #[error("Unimplemented")]
337 Unimplemented,
338
339 #[error(transparent)]
340 Uri(#[from] UriError),
341
342 #[error("Error parsing URL: {0}")]
343 UrlParse(#[from] url::ParseError),
344
345 #[error("UTF-8 error: {0}")]
346 Utf8(#[from] Utf8Error),
347
348 #[error(transparent)]
349 WorkflowValidation(#[from] WorkflowValidationError),
350
351 #[error("YAML error: {0}")]
352 Yaml(#[from] serde_yaml::Error),
353}
354
355impl From<crate::workflow::ConfigError> for Error {
361 fn from(err: crate::workflow::ConfigError) -> Self {
362 use crate::workflow::ConfigError;
363 match err {
364 ConfigError::Workflow(msg) => Error::RemoteCatalog(RemoteCatalogError::Workflow(msg)),
365 ConfigError::InvalidWorkflowsConfig(msg) => {
366 Error::RemoteCatalog(RemoteCatalogError::InvalidWorkflowsConfig(msg))
367 }
368 ConfigError::Uri(err) => Error::Uri(err),
369 }
370 }
371}
372
373impl Error {
374 #[must_use]
379 pub fn is_not_found(&self) -> bool {
380 match self {
381 Error::S3(s3) => s3.is_not_found(),
382 Error::Fs(FsError::NotFound { .. }) => true,
383 Error::Fs(FsError::Read { source, .. }) => {
384 source.kind() == std::io::ErrorKind::NotFound
385 }
386 Error::Io(e) => e.kind() == std::io::ErrorKind::NotFound,
387 _ => false,
388 }
389 }
390
391 #[must_use]
399 pub fn is_access_denied(&self) -> bool {
400 matches!(self, Error::S3(s3) if s3.is_access_denied())
401 }
402}
403
404impl From<multihash::Error> for Error {
409 fn from(err: multihash::Error) -> Self {
410 Error::ObjectHash(err.into())
411 }
412}
413
414impl From<multibase::Error> for Error {
415 fn from(err: multibase::Error) -> Self {
416 Error::ObjectHash(err.into())
417 }
418}
419
420impl From<byte_stream::error::Error> for Error {
421 fn from(err: byte_stream::error::Error) -> Self {
422 FsError::ByteStream(err).into()
423 }
424}
425
426impl From<StripPrefixError> for Error {
427 fn from(err: StripPrefixError) -> Self {
428 Error::Fs(FsError::PathPrefixNotFound(err))
429 }
430}