Skip to main content

fluidattacks_core_git/
errors.rs

1use std::io;
2use std::path::PathBuf;
3use std::process::ExitStatus;
4use std::time::Duration;
5
6/// A file call failed at `path`; `source` keeps the errno.
7#[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/// Removing the symlinks or the out-of-scope files failed.
17#[derive(Debug, thiserror::Error)]
18pub enum PruneError {
19    #[error(transparent)]
20    File(#[from] FileError),
21    /// The git that lists the out-of-scope files failed.
22    #[error(transparent)]
23    Git(#[from] GitCallError),
24}
25
26/// Why `reset_repo` failed.
27#[derive(Debug, thiserror::Error)]
28pub enum ResetError {
29    /// `git reset --hard` failed, as it does for a path that is not the root.
30    #[error(transparent)]
31    Git(#[from] GitCallError),
32    #[error(transparent)]
33    Symlinks(#[from] FileError),
34}
35
36#[derive(Debug, thiserror::Error)]
37pub enum GitCallError {
38    #[error("git {command} could not start: {source}")]
39    NotStarted {
40        command: &'static str,
41        source: io::Error,
42    },
43    #[error("git {command} did not finish within {after:?}")]
44    TimedOut {
45        command: &'static str,
46        after: Duration,
47    },
48    /// git ran and refused, as it does for a missing repository or revision.
49    #[error("git {command} exited with {status}: {stderr}")]
50    Refused {
51        command: &'static str,
52        status: ExitStatus,
53        stderr: String,
54    },
55    /// No process can receive a NUL, so std turns the call down before it starts.
56    #[error("git {command} got an argument with an embedded null byte")]
57    NullByte { command: &'static str },
58    #[error("git {command} printed something unreadable: {reason}")]
59    Malformed {
60        command: &'static str,
61        reason: String,
62    },
63}
64
65/// Why `download_and_extract_repo` failed, by the step that failed.
66#[derive(Debug, thiserror::Error)]
67pub enum DownloadError {
68    /// The parent of the destination, the work directory beside it, or the
69    /// archive inside that directory could not be created or written.
70    #[error("writing the archive: {0}")]
71    Write(#[source] io::Error),
72    #[error(transparent)]
73    Fetch(#[from] FetchError),
74    #[error(transparent)]
75    Extract(#[from] ExtractError),
76    /// `git reset --hard` did not restore the working tree.
77    #[error(transparent)]
78    Reset(#[from] GitCallError),
79    #[error("pruning the working tree: {0}")]
80    Prune(#[from] PruneError),
81    /// The repository could not take the place of the destination.
82    #[error("placing the repository: {0}")]
83    Place(#[source] io::Error),
84}
85
86/// How the request for the archive failed.
87#[derive(Debug, thiserror::Error)]
88pub enum FetchError {
89    #[error("invalid url {url}: {reason}")]
90    InvalidUrl { url: String, reason: String },
91    #[error("not an http url: {0}")]
92    NonHttpUrl(String),
93    /// DNS, a refused connection or a TLS handshake that failed.
94    #[error("could not connect: {0}")]
95    Connect(#[source] reqwest::Error),
96    #[error("connecting timed out: {0}")]
97    ConnectTimeout(#[source] reqwest::Error),
98    /// The total timeout ran out before the response headers arrived.
99    #[error("no response in time: {0}")]
100    Timeout(#[source] reqwest::Error),
101    /// The server closed the connection before it answered.
102    #[error("the server disconnected: {0}")]
103    Disconnected(#[source] reqwest::Error),
104    /// The socket failed while the request was sent; `errno` is the one of
105    /// the socket call.
106    #[error("the socket failed: {source}")]
107    Socket {
108        reset: bool,
109        errno: Option<i32>,
110        source: reqwest::Error,
111    },
112    #[error("malformed response: {0}")]
113    Malformed(#[source] reqwest::Error),
114    #[error("the server answered {0}")]
115    Status(reqwest::StatusCode),
116    /// The body ended before its length, or the connection broke mid-read.
117    #[error("the body was cut: {0}")]
118    BodyCut(#[source] reqwest::Error),
119    /// The total timeout ran out while the body was read.
120    #[error("the body did not arrive in time: {0}")]
121    BodyTimeout(#[source] reqwest::Error),
122    #[error("the request failed: {0}")]
123    Other(#[source] reqwest::Error),
124}
125
126/// How turning the archive into a repository failed.
127#[derive(Debug, thiserror::Error)]
128pub enum ExtractError {
129    /// The archive is not gzip, its tar or deflate data is broken, or its
130    /// checksum does not match.
131    #[error("corrupt archive: {0}")]
132    Corrupt(#[source] io::Error),
133    /// The gzip stream ends before its trailer.
134    #[error("truncated archive: {0}")]
135    Truncated(#[source] io::Error),
136    /// The archive holds no top directory with a real `.git` directory.
137    #[error("the archive holds no repository: {0}")]
138    NoRepository(String),
139    #[error("extracting the archive: {0}")]
140    Io(#[source] io::Error),
141}