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