git_bot_feedback/
error.rs1#[cfg(feature = "pyo3")]
3use pyo3::{
4 exceptions::{PyOSError, PyRuntimeError, PyValueError},
5 prelude::*,
6};
7
8#[cfg(feature = "file-changes")]
9use std::path::PathBuf;
10
11use chrono::{DateTime, Utc};
12use thiserror::Error;
13
14use crate::client::MAX_RETRIES;
15
16#[derive(Debug, thiserror::Error)]
18#[cfg(feature = "file-changes")]
19#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
20pub enum DiffError {
21 #[error("Failed to compile regex pattern: {0}")]
23 RegExCompileFailed(#[from] regex::Error),
24
25 #[error("Unrecognized diff starting with: {0}")]
27 MalformedDiffError(String),
28}
29
30#[derive(Debug, thiserror::Error, PartialEq, Eq)]
32pub enum OutputVariableError {
33 #[error("The output variable's name is empty")]
35 NameIsEmpty,
36 #[error("The output variable's name starts with a number: '{0}'")]
38 NameStartsWithNumber(String),
39 #[error("The output variable's name contains non-printable characters: '{0}'")]
41 NameContainsNonPrintableCharacters(String),
42 #[error("The output variable's value contains non-printable characters: '{0}'")]
44 ValueContainsNonPrintableCharacters(String),
45 #[error("Unsupported CI platform")]
47 UnsupportedPlatform,
48}
49
50#[derive(Debug, Error)]
52pub enum RestClientError {
53 #[error(transparent)]
55 #[cfg(feature = "file-changes")]
56 #[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
57 DiffError(#[from] DiffError),
58
59 #[error("Encountered malformed event info: {0}")]
61 MalformedEventInfo(String),
62
63 #[error(transparent)]
65 Request(#[from] reqwest::Error),
66
67 #[error("Failed to {task}: {source}")]
69 RequestContext {
70 task: String,
72 #[source]
74 source: reqwest::Error,
75 },
76
77 #[error("Failed to {task}: {source}")]
79 Io {
80 task: String,
82 #[source]
84 source: std::io::Error,
85 },
86
87 #[error("Git command error: {0}")]
89 #[cfg(feature = "file-changes")]
90 #[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
91 GitCommand(String),
92
93 #[error("Primary Rate Limit exceeded (no reset time provided)")]
96 RateLimitNoReset,
97
98 #[error("Primary Rate Limit exceeded; resets at {0}")]
100 RateLimitPrimary(DateTime<Utc>),
101
102 #[error("Rate Limit exceeded after all {MAX_RETRIES} retries exhausted")]
104 RateLimitSecondary,
105
106 #[error("Failed to clone request object for auto-retries")]
108 CannotCloneRequest,
109
110 #[error("Tried to create a header value from invalid string data")]
112 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
113
114 #[error("Failed to convert header value to string")]
116 UnexpectedHeaderValue(#[from] reqwest::header::ToStrError),
117
118 #[error("Failed to parse integer from header value: {0}")]
120 HeaderParseInt(#[from] std::num::ParseIntError),
121
122 #[error("Failed to parse URL:{0}")]
124 UrlParse(#[from] url::ParseError),
125
126 #[error("Failed to {task}: {source}")]
128 Json {
129 task: String,
131 #[source]
133 source: serde_json::Error,
134 },
135
136 #[error("Failed to get env var '{name}': {source}")]
138 EnvVar {
139 name: String,
141 #[source]
143 source: std::env::VarError,
144 },
145
146 #[error("OutputVariable is malformed: {0}")]
148 OutputVar(#[from] OutputVariableError),
149}
150
151impl RestClientError {
152 pub fn env_var(name: &str, source: std::env::VarError) -> Self {
154 Self::EnvVar {
155 name: name.to_string(),
156 source,
157 }
158 }
159
160 pub fn io(task: &str, source: std::io::Error) -> Self {
162 Self::Io {
163 task: task.to_string(),
164 source,
165 }
166 }
167
168 pub fn add_request_context(self, task: &str) -> Self {
173 match self {
174 Self::Request(e) => Self::RequestContext {
175 task: task.to_string(),
176 source: e,
177 },
178 _ => self,
179 }
180 }
181
182 pub fn json(task: &str, source: serde_json::Error) -> Self {
184 Self::Json {
185 task: task.to_string(),
186 source,
187 }
188 }
189}
190
191#[cfg(feature = "file-changes")]
196#[derive(Debug, Error)]
197#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
198pub enum DirWalkError {
199 #[error("Failed to read {path}: {source}")]
201 ReadDir {
202 path: PathBuf,
204 #[source]
206 source: std::io::Error,
207 },
208
209 #[error(transparent)]
211 OsError(#[from] std::io::Error),
212}
213
214#[cfg(feature = "pyo3")]
215impl From<OutputVariableError> for PyErr {
216 fn from(e: OutputVariableError) -> Self {
217 match e {
218 OutputVariableError::NameIsEmpty
219 | OutputVariableError::NameStartsWithNumber(_)
220 | OutputVariableError::NameContainsNonPrintableCharacters(_)
221 | OutputVariableError::ValueContainsNonPrintableCharacters(_) => {
222 PyValueError::new_err(format!("{e:?}"))
223 }
224 OutputVariableError::UnsupportedPlatform => PyRuntimeError::new_err(format!("{e:?}")),
225 }
226 }
227}
228
229#[cfg(feature = "pyo3")]
230impl From<DiffError> for PyErr {
231 fn from(e: DiffError) -> Self {
232 match e {
233 DiffError::RegExCompileFailed(_) | DiffError::MalformedDiffError(_) => {
234 PyValueError::new_err(format!("{e:?}"))
235 }
236 }
237 }
238}
239
240#[cfg(feature = "pyo3")]
241impl From<DirWalkError> for PyErr {
242 fn from(e: DirWalkError) -> Self {
243 PyOSError::new_err(format!("{e:?}"))
244 }
245}
246
247#[cfg(feature = "pyo3")]
248impl From<RestClientError> for PyErr {
249 fn from(err: RestClientError) -> Self {
250 match err {
251 #[cfg(feature = "file-changes")]
252 RestClientError::DiffError(e) => e.into(),
253 RestClientError::MalformedEventInfo(_) => PyRuntimeError::new_err(format!("{err:?}")),
254 RestClientError::Request(e) => PyOSError::new_err(format!("{e:?}")),
255 RestClientError::RequestContext { task: _, source: _ }
256 | RestClientError::Io { task: _, source: _ }
257 | RestClientError::RateLimitNoReset
258 | RestClientError::RateLimitPrimary(_)
259 | RestClientError::RateLimitSecondary => PyOSError::new_err(format!("{err:?}")),
260 RestClientError::CannotCloneRequest
261 | RestClientError::InvalidHeaderValue(_)
262 | RestClientError::UnexpectedHeaderValue(_)
263 | RestClientError::HeaderParseInt(_)
264 | RestClientError::UrlParse(_)
265 | RestClientError::Json { task: _, source: _ }
266 | RestClientError::EnvVar { name: _, source: _ } => {
267 PyValueError::new_err(format!("{err:?}"))
268 }
269 #[cfg(feature = "file-changes")]
270 RestClientError::GitCommand(_) => PyValueError::new_err(format!("{err:?}")),
271 RestClientError::OutputVar(e) => e.into(),
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::RestClientError;
279
280 #[test]
281 fn no_added_req_ctx() {
282 let err = RestClientError::CannotCloneRequest;
283 assert!(matches!(
284 err.add_request_context("some task"),
285 RestClientError::CannotCloneRequest
286 ));
287 }
288}