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
26#[derive(Debug, thiserror::Error, PartialEq, Eq)]
28pub enum OutputVariableError {
29 #[error("The output variable's name is empty")]
31 NameIsEmpty,
32 #[error("The output variable's name starts with a number: '{0}'")]
34 NameStartsWithNumber(String),
35 #[error("The output variable's name contains non-printable characters: '{0}'")]
37 NameContainsNonPrintableCharacters(String),
38 #[error("The output variable's value contains non-printable characters: '{0}'")]
40 ValueContainsNonPrintableCharacters(String),
41 #[error("Unsupported CI platform")]
43 UnsupportedPlatform,
44}
45
46#[derive(Debug, Error)]
48pub enum RestClientError {
49 #[error(transparent)]
51 #[cfg(feature = "file-changes")]
52 #[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
53 DiffError(#[from] DiffError),
54
55 #[error("Encountered malformed event info: {0}")]
57 MalformedEventInfo(String),
58
59 #[error(transparent)]
61 Request(#[from] reqwest::Error),
62
63 #[error("Failed to {task}: {source}")]
65 RequestContext {
66 task: String,
68 #[source]
70 source: reqwest::Error,
71 },
72
73 #[error("Failed to {task}: {source}")]
75 Io {
76 task: String,
78 #[source]
80 source: std::io::Error,
81 },
82
83 #[error("Git command error: {0}")]
85 #[cfg(feature = "file-changes")]
86 #[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
87 GitCommand(String),
88
89 #[error("Primary Rate Limit exceeded (no reset time provided)")]
92 RateLimitNoReset,
93
94 #[error("Primary Rate Limit exceeded; resets at {0}")]
96 RateLimitPrimary(DateTime<Utc>),
97
98 #[error("Rate Limit exceeded after all {MAX_RETRIES} retries exhausted")]
100 RateLimitSecondary,
101
102 #[error("Failed to clone request object for auto-retries")]
104 CannotCloneRequest,
105
106 #[error("Tried to create a header value from invalid string data")]
108 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
109
110 #[error("Failed to convert header value to string")]
112 UnexpectedHeaderValue(#[from] reqwest::header::ToStrError),
113
114 #[error("Failed to parse integer from header value: {0}")]
116 HeaderParseInt(#[from] std::num::ParseIntError),
117
118 #[error("Failed to parse URL:{0}")]
120 UrlParse(#[from] url::ParseError),
121
122 #[error("Failed to {task}: {source}")]
124 Json {
125 task: String,
127 #[source]
129 source: serde_json::Error,
130 },
131
132 #[error("Failed to get env var '{name}': {source}")]
134 EnvVar {
135 name: String,
137 #[source]
139 source: std::env::VarError,
140 },
141
142 #[error("OutputVariable is malformed: {0}")]
144 OutputVar(#[from] OutputVariableError),
145}
146
147impl RestClientError {
148 pub fn env_var(name: &str, source: std::env::VarError) -> Self {
150 Self::EnvVar {
151 name: name.to_string(),
152 source,
153 }
154 }
155
156 pub fn io(task: &str, source: std::io::Error) -> Self {
158 Self::Io {
159 task: task.to_string(),
160 source,
161 }
162 }
163
164 pub fn add_request_context(self, task: &str) -> Self {
169 match self {
170 Self::Request(e) => Self::RequestContext {
171 task: task.to_string(),
172 source: e,
173 },
174 _ => self,
175 }
176 }
177
178 pub fn json(task: &str, source: serde_json::Error) -> Self {
180 Self::Json {
181 task: task.to_string(),
182 source,
183 }
184 }
185}
186
187#[cfg(feature = "file-changes")]
192#[derive(Debug, Error)]
193#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
194pub enum DirWalkError {
195 #[error("Failed to read {path}: {source}")]
197 ReadDir {
198 path: PathBuf,
200 #[source]
202 source: std::io::Error,
203 },
204
205 #[error(transparent)]
207 OsError(#[from] std::io::Error),
208}
209
210#[cfg(feature = "pyo3")]
211impl From<OutputVariableError> for PyErr {
212 fn from(e: OutputVariableError) -> Self {
213 match e {
214 OutputVariableError::NameIsEmpty
215 | OutputVariableError::NameStartsWithNumber(_)
216 | OutputVariableError::NameContainsNonPrintableCharacters(_)
217 | OutputVariableError::ValueContainsNonPrintableCharacters(_) => {
218 PyValueError::new_err(format!("{e:?}"))
219 }
220 OutputVariableError::UnsupportedPlatform => PyRuntimeError::new_err(format!("{e:?}")),
221 }
222 }
223}
224
225#[cfg(feature = "pyo3")]
226impl From<DiffError> for PyErr {
227 fn from(e: DiffError) -> Self {
228 match e {
229 DiffError::RegExCompileFailed(_) => PyValueError::new_err(format!("{e:?}")),
230 }
231 }
232}
233
234#[cfg(feature = "pyo3")]
235impl From<DirWalkError> for PyErr {
236 fn from(e: DirWalkError) -> Self {
237 PyOSError::new_err(format!("{e:?}"))
238 }
239}
240
241#[cfg(feature = "pyo3")]
242impl From<RestClientError> for PyErr {
243 fn from(err: RestClientError) -> Self {
244 match err {
245 #[cfg(feature = "file-changes")]
246 RestClientError::DiffError(e) => e.into(),
247 RestClientError::MalformedEventInfo(_) => PyRuntimeError::new_err(format!("{err:?}")),
248 RestClientError::Request(e) => PyOSError::new_err(format!("{e:?}")),
249 RestClientError::RequestContext { task: _, source: _ }
250 | RestClientError::Io { task: _, source: _ }
251 | RestClientError::RateLimitNoReset
252 | RestClientError::RateLimitPrimary(_)
253 | RestClientError::RateLimitSecondary => PyOSError::new_err(format!("{err:?}")),
254 RestClientError::CannotCloneRequest
255 | RestClientError::InvalidHeaderValue(_)
256 | RestClientError::UnexpectedHeaderValue(_)
257 | RestClientError::HeaderParseInt(_)
258 | RestClientError::UrlParse(_)
259 | RestClientError::Json { task: _, source: _ }
260 | RestClientError::EnvVar { name: _, source: _ } => {
261 PyValueError::new_err(format!("{err:?}"))
262 }
263 #[cfg(feature = "file-changes")]
264 RestClientError::GitCommand(_) => PyValueError::new_err(format!("{err:?}")),
265 RestClientError::OutputVar(e) => e.into(),
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::RestClientError;
273
274 #[test]
275 fn no_added_req_ctx() {
276 let err = RestClientError::CannotCloneRequest;
277 assert!(matches!(
278 err.add_request_context("some task"),
279 RestClientError::CannotCloneRequest
280 ));
281 }
282}