1use std::io;
2use std::path::PathBuf;
3use std::process::ExitStatus;
4use std::time::Duration;
5
6#[derive(Debug, thiserror::Error)]
8#[error("{action} {}: {source}", path.display())]
9pub struct FileError {
10 pub action: &'static str,
11 pub path: PathBuf,
12 #[source]
13 pub source: io::Error,
14}
15
16#[derive(Debug, thiserror::Error)]
18pub enum PruneError {
19 #[error(transparent)]
20 File(#[from] FileError),
21 #[error(transparent)]
23 Git(#[from] GitCallError),
24}
25
26#[derive(Debug, thiserror::Error)]
28pub enum ResetError {
29 #[error(transparent)]
31 Git(#[from] GitCallError),
32 #[error(transparent)]
33 Symlinks(#[from] FileError),
34}
35
36#[derive(Debug, PartialEq, Eq, thiserror::Error)]
38pub enum HttpsAccessError {
39 #[error("Invalid credentials: a line break or a nul byte")]
41 CredentialLineBreak,
42 #[error("expected an http or https url")]
43 NotHttp,
44 #[error("Invalid url: its host or port could not be parsed")]
46 Unparsable,
47 #[error("expected an https credential")]
48 NotHttpsCredential,
49}
50
51#[derive(Debug, thiserror::Error)]
53pub enum CloneError {
54 #[error(transparent)]
55 Access(#[from] HttpsAccessError),
56 #[error(transparent)]
57 Git(#[from] GitCallError),
58 #[error(transparent)]
60 Info(#[from] FileError),
61 #[error(transparent)]
62 SshKey(#[from] SshKeyError),
63 #[error(transparent)]
64 Sts(#[from] StsError),
65}
66
67#[derive(Debug, thiserror::Error)]
69pub enum SshKeyError {
70 #[error("the SSH key is not base64: {0}")]
71 Decode(#[from] base64::DecodeError),
72 #[error("the SSH key is empty")]
73 Empty,
74 #[error("the SSH key could not be written: {0}")]
75 File(#[source] io::Error),
76 #[error("the SSH key path cannot be quoted for the shell: {0}")]
77 Path(#[from] shlex::QuoteError),
78}
79
80#[derive(Debug, thiserror::Error)]
82pub enum LsRemoteError {
83 #[error(transparent)]
84 SshKey(#[from] SshKeyError),
85 #[error(transparent)]
86 Sts(#[from] StsError),
87 #[error(transparent)]
88 Access(#[from] HttpsAccessError),
89 #[error(transparent)]
90 Git(#[from] GitCallError),
91}
92
93#[derive(Debug, thiserror::Error)]
95pub(crate) enum RemoteAccessError {
96 #[error(transparent)]
97 SshKey(#[from] SshKeyError),
98 #[error(transparent)]
99 Sts(#[from] StsError),
100 #[error(transparent)]
101 Access(#[from] HttpsAccessError),
102}
103
104impl From<RemoteAccessError> for LsRemoteError {
105 fn from(error: RemoteAccessError) -> Self {
106 match error {
107 RemoteAccessError::SshKey(error) => Self::SshKey(error),
108 RemoteAccessError::Sts(error) => Self::Sts(error),
109 RemoteAccessError::Access(error) => Self::Access(error),
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
116pub enum LocalAddress {
117 #[error("Requests to loopback addresses are not allowed")]
118 Loopback,
119 #[error("Requests to the local network are not allowed")]
120 LocalNetwork,
121}
122
123#[derive(Debug, thiserror::Error)]
125pub enum HttpsLsRemoteError {
126 #[error(transparent)]
128 Host(#[from] HostError),
129 #[error(transparent)]
130 Access(#[from] HttpsAccessError),
131 #[error(transparent)]
132 Git(#[from] GitCallError),
133}
134
135#[derive(Debug, thiserror::Error)]
137pub enum HostError {
138 #[error("Invalid url: its host could not be read")]
139 Unparsable,
140 #[error("could not resolve the hostname: {0}")]
141 Unresolved(#[source] io::Error),
142 #[error("no IP addresses resolved for the hostname")]
143 NoAddresses,
144 #[error(transparent)]
145 Local(LocalAddress),
146}
147
148#[derive(Debug, thiserror::Error)]
150pub enum RedirectError {
151 #[error("Invalid url: {0}")]
153 Unparsable(String),
154 #[error("expected an http or https url")]
155 NotHttp,
156 #[error("Hostname or IP address invalid")]
157 InvalidHost,
158 #[error("expected an https credential")]
159 NotHttpsCredential,
160 #[error("Invalid credentials: a line break or another character a header cannot carry")]
161 CredentialNotAHeader,
162 #[error(transparent)]
163 Local(LocalAddress),
164 #[error("no response in time: {0}")]
165 Timeout(#[source] reqwest::Error),
166 #[error("the request failed: {0}")]
168 Request(#[source] reqwest::Error),
169}
170
171#[derive(Debug, thiserror::Error)]
173pub enum StsError {
174 #[error("STS refused the role {arn}: {code}: {message}")]
176 Refused {
177 arn: String,
178 code: String,
179 message: String,
180 },
181 #[error("STS took too long to connect for the role {arn}: {reason}")]
182 ConnectTimeout { arn: String, reason: String },
183 #[error("STS took too long to answer the role {arn}: {reason}")]
185 ReadTimeout { arn: String, reason: String },
186 #[error("STS closed the connection for the role {arn}: {reason}")]
188 ConnectionClosed { arn: String, reason: String },
189 #[error("STS could not be connected to for the role {arn}: {reason}")]
191 NoConnection { arn: String, reason: String },
192 #[error("STS could not be asked for the role {arn}: {reason}")]
194 Unreachable { arn: String, reason: String },
195 #[error("STS answered the role {arn} without credentials")]
196 NoCredentials { arn: String },
197}
198
199#[derive(Debug, thiserror::Error)]
200pub enum GitCallError {
201 #[error("git {command} could not start: {source}")]
202 NotStarted {
203 command: &'static str,
204 source: io::Error,
205 },
206 #[error("git {command} did not finish within {after:?}")]
207 TimedOut {
208 command: &'static str,
209 after: Duration,
210 },
211 #[error("git {command} exited with {status}: {stderr}")]
213 Refused {
214 command: &'static str,
215 status: ExitStatus,
216 stderr: String,
217 },
218 #[error("git {command} got an argument with an embedded null byte")]
220 NullByte { command: &'static str },
221 #[error("git {command} printed something unreadable: {reason}")]
222 Malformed {
223 command: &'static str,
224 reason: String,
225 },
226}
227
228#[derive(Debug, thiserror::Error)]
230pub enum DownloadError {
231 #[error("writing the archive: {0}")]
234 Write(#[source] io::Error),
235 #[error(transparent)]
236 Fetch(#[from] FetchError),
237 #[error(transparent)]
238 Extract(#[from] ExtractError),
239 #[error(transparent)]
241 Reset(#[from] GitCallError),
242 #[error("pruning the working tree: {0}")]
243 Prune(#[from] PruneError),
244 #[error("placing the repository: {0}")]
247 Place(#[source] io::Error),
248}
249
250#[derive(Debug, thiserror::Error)]
252pub enum FetchError {
253 #[error("invalid url {url}: {reason}")]
254 InvalidUrl { url: String, reason: String },
255 #[error("not an http url: {0}")]
256 NonHttpUrl(String),
257 #[error("could not connect: {0}")]
259 Connect(#[source] reqwest::Error),
260 #[error("connecting timed out: {0}")]
261 ConnectTimeout(#[source] reqwest::Error),
262 #[error("no response in time: {0}")]
264 Timeout(#[source] reqwest::Error),
265 #[error("the server disconnected: {0}")]
267 Disconnected(#[source] reqwest::Error),
268 #[error("the socket failed: {source}")]
271 Socket {
272 reset: bool,
273 errno: Option<i32>,
274 source: reqwest::Error,
275 },
276 #[error("malformed response: {0}")]
277 Malformed(#[source] reqwest::Error),
278 #[error("the server answered {0}")]
279 Status(reqwest::StatusCode),
280 #[error("the body was cut: {0}")]
282 BodyCut(#[source] reqwest::Error),
283 #[error("the body did not arrive in time: {0}")]
285 BodyTimeout(#[source] reqwest::Error),
286 #[error("the request failed: {0}")]
287 Other(#[source] reqwest::Error),
288}
289
290#[derive(Debug, thiserror::Error)]
292pub enum ExtractError {
293 #[error("corrupt archive: {0}")]
296 Corrupt(#[source] io::Error),
297 #[error("truncated archive: {0}")]
299 Truncated(#[source] io::Error),
300 #[error("the archive holds no repository: {0}")]
302 NoRepository(String),
303 #[error("extracting the archive: {0}")]
304 Io(#[source] io::Error),
305}