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