Skip to main content

git_bot_feedback/
error.rs

1//! Error types used across the git-bot-feedback crate.
2#[cfg(feature = "file-changes")]
3use std::path::PathBuf;
4
5use chrono::{DateTime, Utc};
6use thiserror::Error;
7
8use crate::client::MAX_RETRIES;
9
10/// The possible errors emitted when validating an [`OutputVariable`](struct@crate::OutputVariable).
11#[derive(Debug, thiserror::Error, PartialEq, Eq)]
12pub enum OutputVariableError {
13    /// The output variable's name is empty.
14    #[error("The output variable's name is empty")]
15    NameIsEmpty,
16    /// The output variable's name starts with a number.
17    #[error("The output variable's name starts with a number: '{0}'")]
18    NameStartsWithNumber(String),
19    /// The output variable's name contains non-printable characters.
20    #[error("The output variable's name contains non-printable characters: '{0}'")]
21    NameContainsNonPrintableCharacters(String),
22    /// The output variable's value contains non-printable characters.
23    #[error("The output variable's value contains non-printable characters: '{0}'")]
24    ValueContainsNonPrintableCharacters(String),
25}
26
27/// The possible error emitted by the REST client API
28#[derive(Debug, Error)]
29pub enum RestClientError {
30    /// Error related to making HTTP requests
31    #[error(transparent)]
32    Request(#[from] reqwest::Error),
33
34    /// Error related to making HTTP requests, with additional context about the request that caused the error.
35    #[error("Failed to {task}: {source}")]
36    RequestContext {
37        task: String,
38        #[source]
39        source: reqwest::Error,
40    },
41
42    /// Errors related to standard I/O.
43    #[error("Failed to {task}: {source}")]
44    Io {
45        task: String,
46        #[source]
47        source: std::io::Error,
48    },
49
50    /// Error related to `git` command execution.
51    #[error("Git command error: {0}")]
52    #[cfg(feature = "file-changes")]
53    #[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
54    GitCommand(String),
55
56    /// Error related to exceeding REST API Rate limits and
57    /// no reset time is provided in the response headers.
58    #[error("Primary Rate Limit exceeded (no reset time provided)")]
59    RateLimitNoReset,
60
61    /// Error related to exceeding REST API Rate limits with a known reset time.
62    #[error("Primary Rate Limit exceeded; resets at {0}")]
63    RateLimitPrimary(DateTime<Utc>),
64
65    /// Error related to exhausting all retries after hitting REST API Rate limits.
66    #[error("Rate Limit exceeded after all {MAX_RETRIES} retries exhausted")]
67    RateLimitSecondary,
68
69    /// Error emitted when failing to clone a request object.
70    #[error("Failed to clone request object for auto-retries")]
71    CannotCloneRequest,
72
73    /// Error emitted when failing to create a header value.
74    #[error("Tried to create a header value from invalid string data")]
75    InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
76
77    /// Error emitted when failing to convert a header value to string.
78    #[error("Failed to convert header value to string")]
79    UnexpectedHeaderValue(#[from] reqwest::header::ToStrError),
80
81    /// Error emitted when failing to parse an integer from a header value (as a UTF-8 string).
82    #[error("Failed to parse integer from header value: {0}")]
83    HeaderParseInt(#[from] std::num::ParseIntError),
84
85    /// Error emitted when failing to parse a URL.
86    #[error("Failed to parse URL:{0}")]
87    UrlParse(#[from] url::ParseError),
88
89    /// Error emitted when failing to deserialize/serialize request/response JSON data.
90    #[error("Failed to {task}: {source}")]
91    Json {
92        task: String,
93        #[source]
94        source: serde_json::Error,
95    },
96
97    /// Error emitted when failing to read an environment variable.
98    #[error("Failed to get env var '{name}': {source}")]
99    EnvVar {
100        name: String,
101        #[source]
102        source: std::env::VarError,
103    },
104
105    /// An error emitted when encountering an invalid [`OutputVariable`](crate::output_variable::OutputVariable).
106    #[error("OutputVariable is malformed: {0}")]
107    OutputVar(#[from] OutputVariableError),
108}
109
110impl RestClientError {
111    /// Helper function to create an [`Self::EnvVar`] error with variable name and source error.
112    pub fn env_var(name: &str, source: std::env::VarError) -> Self {
113        Self::EnvVar {
114            name: name.to_string(),
115            source,
116        }
117    }
118
119    /// Helper function to create an [`Self::Io`] error with task context.
120    pub fn io(task: &str, source: std::io::Error) -> Self {
121        Self::Io {
122            task: task.to_string(),
123            source,
124        }
125    }
126
127    /// Builder function to add context to [`Self::Request`] errors.
128    ///
129    /// Returns a [`Self::RequestContext`] error if `self` is a [`Self::Request`] error.
130    /// Otherwise, returns `self` unchanged.
131    pub fn add_request_context(self, task: &str) -> Self {
132        match self {
133            Self::Request(e) => Self::RequestContext {
134                task: task.to_string(),
135                source: e,
136            },
137            _ => self,
138        }
139    }
140
141    /// Helper function to create a [`Self::Json`] error with task context.
142    pub fn json(task: &str, source: serde_json::Error) -> Self {
143        Self::Json {
144            task: task.to_string(),
145            source,
146        }
147    }
148}
149
150/// The possible errors emitted by file system operations.
151///
152/// This is only used (via [`FileFilter::walk()`](fn@crate::file_utils::file_filter::FileFilter::walk_dir));
153/// typically when not running within a supported CI environment.
154#[cfg(feature = "file-changes")]
155#[derive(Debug, Error)]
156#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
157pub enum DirWalkError {
158    #[error("Failed to read {path}: {source}")]
159    ReadDir {
160        path: PathBuf,
161        #[source]
162        source: std::io::Error,
163    },
164
165    #[error(transparent)]
166    OsError(#[from] std::io::Error),
167}
168
169#[cfg(test)]
170mod tests {
171    use super::RestClientError;
172
173    #[test]
174    fn no_added_req_ctx() {
175        let err = RestClientError::CannotCloneRequest;
176        assert!(matches!(
177            err.add_request_context("some task"),
178            RestClientError::CannotCloneRequest
179        ));
180    }
181}