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, Clone, Copy, PartialEq, Eq, thiserror::Error)]
95pub enum LocalAddress {
96 #[error("Requests to loopback addresses are not allowed")]
97 Loopback,
98 #[error("Requests to the local network are not allowed")]
99 LocalNetwork,
100}
101
102#[derive(Debug, thiserror::Error)]
104pub enum HostError {
105 #[error("could not resolve the hostname: {0}")]
106 Unresolved(#[source] io::Error),
107 #[error("no IP addresses resolved for the hostname")]
108 NoAddresses,
109 #[error(transparent)]
110 Local(LocalAddress),
111}
112
113#[derive(Debug, thiserror::Error)]
115pub enum SshUrlError {
116 #[error("Invalid ssh url: expected user@host:path or ssh://user@host[:port]/path")]
118 Malformed,
119 #[error("SSH host validation failed for {host}: {source}")]
120 Refused {
121 host: String,
122 #[source]
123 source: HostError,
124 },
125}
126
127#[derive(Debug, thiserror::Error)]
129pub enum RedirectError {
130 #[error("Invalid url: {0}")]
132 Unparsable(String),
133 #[error("expected an http or https url")]
134 NotHttp,
135 #[error("Hostname or IP address invalid")]
136 InvalidHost,
137 #[error("expected an https credential")]
138 NotHttpsCredential,
139 #[error("Invalid credentials: a line break or another character a header cannot carry")]
140 CredentialNotAHeader,
141 #[error(transparent)]
142 Local(LocalAddress),
143 #[error("no response in time: {0}")]
144 Timeout(#[source] reqwest::Error),
145 #[error("the request failed: {0}")]
147 Request(#[source] reqwest::Error),
148}
149
150#[derive(Debug, thiserror::Error)]
152pub enum StsError {
153 #[error("STS refused the role {arn}: {code}: {message}")]
155 Refused {
156 arn: String,
157 code: String,
158 message: String,
159 },
160 #[error("STS took too long to connect for the role {arn}: {reason}")]
161 ConnectTimeout { arn: String, reason: String },
162 #[error("STS took too long to answer the role {arn}: {reason}")]
164 ReadTimeout { arn: String, reason: String },
165 #[error("STS closed the connection for the role {arn}: {reason}")]
167 ConnectionClosed { arn: String, reason: String },
168 #[error("STS could not be connected to for the role {arn}: {reason}")]
170 NoConnection { arn: String, reason: String },
171 #[error("STS could not be asked for the role {arn}: {reason}")]
173 Unreachable { arn: String, reason: String },
174 #[error("STS answered the role {arn} without credentials")]
175 NoCredentials { arn: String },
176}
177
178#[derive(Debug, thiserror::Error)]
179pub enum GitCallError {
180 #[error("git {command} could not start: {source}")]
181 NotStarted {
182 command: &'static str,
183 source: io::Error,
184 },
185 #[error("git {command} did not finish within {after:?}")]
186 TimedOut {
187 command: &'static str,
188 after: Duration,
189 },
190 #[error("git {command} exited with {status}: {stderr}")]
192 Refused {
193 command: &'static str,
194 status: ExitStatus,
195 stderr: String,
196 },
197 #[error("git {command} got an argument with an embedded null byte")]
199 NullByte { command: &'static str },
200 #[error("git {command} printed something unreadable: {reason}")]
201 Malformed {
202 command: &'static str,
203 reason: String,
204 },
205}
206
207#[derive(Debug, thiserror::Error)]
209pub enum DownloadError {
210 #[error("writing the archive: {0}")]
213 Write(#[source] io::Error),
214 #[error(transparent)]
215 Fetch(#[from] FetchError),
216 #[error(transparent)]
217 Extract(#[from] ExtractError),
218 #[error(transparent)]
220 Reset(#[from] GitCallError),
221 #[error("pruning the working tree: {0}")]
222 Prune(#[from] PruneError),
223 #[error("placing the repository: {0}")]
226 Place(#[source] io::Error),
227}
228
229#[derive(Debug, thiserror::Error)]
231pub enum FetchError {
232 #[error("invalid url {url}: {reason}")]
233 InvalidUrl { url: String, reason: String },
234 #[error("not an http url: {0}")]
235 NonHttpUrl(String),
236 #[error("could not connect: {0}")]
238 Connect(#[source] reqwest::Error),
239 #[error("connecting timed out: {0}")]
240 ConnectTimeout(#[source] reqwest::Error),
241 #[error("no response in time: {0}")]
243 Timeout(#[source] reqwest::Error),
244 #[error("the server disconnected: {0}")]
246 Disconnected(#[source] reqwest::Error),
247 #[error("the socket failed: {source}")]
250 Socket {
251 reset: bool,
252 errno: Option<i32>,
253 source: reqwest::Error,
254 },
255 #[error("malformed response: {0}")]
256 Malformed(#[source] reqwest::Error),
257 #[error("the server answered {0}")]
258 Status(reqwest::StatusCode),
259 #[error("the body was cut: {0}")]
261 BodyCut(#[source] reqwest::Error),
262 #[error("the body did not arrive in time: {0}")]
264 BodyTimeout(#[source] reqwest::Error),
265 #[error("the request failed: {0}")]
266 Other(#[source] reqwest::Error),
267}
268
269#[derive(Debug, thiserror::Error)]
271pub enum ExtractError {
272 #[error("corrupt archive: {0}")]
275 Corrupt(#[source] io::Error),
276 #[error("truncated archive: {0}")]
278 Truncated(#[source] io::Error),
279 #[error("the archive holds no repository: {0}")]
281 NoRepository(String),
282 #[error("extracting the archive: {0}")]
283 Io(#[source] io::Error),
284}