#[cfg(feature = "pyo3")]
use pyo3::{
exceptions::{PyOSError, PyRuntimeError, PyValueError},
prelude::*,
};
#[cfg(feature = "file-changes")]
use std::path::PathBuf;
use chrono::{DateTime, Utc};
use thiserror::Error;
use crate::client::MAX_RETRIES;
#[derive(Debug, thiserror::Error)]
#[cfg(feature = "file-changes")]
#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
pub enum DiffError {
#[error("Failed to compile regex pattern: {0}")]
RegExCompileFailed(#[from] regex::Error),
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum OutputVariableError {
#[error("The output variable's name is empty")]
NameIsEmpty,
#[error("The output variable's name starts with a number: '{0}'")]
NameStartsWithNumber(String),
#[error("The output variable's name contains non-printable characters: '{0}'")]
NameContainsNonPrintableCharacters(String),
#[error("The output variable's value contains non-printable characters: '{0}'")]
ValueContainsNonPrintableCharacters(String),
#[error("Unsupported CI platform")]
UnsupportedPlatform,
}
#[derive(Debug, Error)]
pub enum RestClientError {
#[error(transparent)]
#[cfg(feature = "file-changes")]
#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
DiffError(#[from] DiffError),
#[error("Encountered malformed event info: {0}")]
MalformedEventInfo(String),
#[error(transparent)]
Request(#[from] reqwest::Error),
#[error("Failed to {task}: {source}")]
RequestContext {
task: String,
#[source]
source: reqwest::Error,
},
#[error("Failed to {task}: {source}")]
Io {
task: String,
#[source]
source: std::io::Error,
},
#[error("Git command error: {0}")]
#[cfg(feature = "file-changes")]
#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
GitCommand(String),
#[error("Primary Rate Limit exceeded (no reset time provided)")]
RateLimitNoReset,
#[error("Primary Rate Limit exceeded; resets at {0}")]
RateLimitPrimary(DateTime<Utc>),
#[error("Rate Limit exceeded after all {MAX_RETRIES} retries exhausted")]
RateLimitSecondary,
#[error("Failed to clone request object for auto-retries")]
CannotCloneRequest,
#[error("Tried to create a header value from invalid string data")]
InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
#[error("Failed to convert header value to string")]
UnexpectedHeaderValue(#[from] reqwest::header::ToStrError),
#[error("Failed to parse integer from header value: {0}")]
HeaderParseInt(#[from] std::num::ParseIntError),
#[error("Failed to parse URL:{0}")]
UrlParse(#[from] url::ParseError),
#[error("Failed to {task}: {source}")]
Json {
task: String,
#[source]
source: serde_json::Error,
},
#[error("Failed to get env var '{name}': {source}")]
EnvVar {
name: String,
#[source]
source: std::env::VarError,
},
#[error("OutputVariable is malformed: {0}")]
OutputVar(#[from] OutputVariableError),
}
impl RestClientError {
pub fn env_var(name: &str, source: std::env::VarError) -> Self {
Self::EnvVar {
name: name.to_string(),
source,
}
}
pub fn io(task: &str, source: std::io::Error) -> Self {
Self::Io {
task: task.to_string(),
source,
}
}
pub fn add_request_context(self, task: &str) -> Self {
match self {
Self::Request(e) => Self::RequestContext {
task: task.to_string(),
source: e,
},
_ => self,
}
}
pub fn json(task: &str, source: serde_json::Error) -> Self {
Self::Json {
task: task.to_string(),
source,
}
}
}
#[cfg(feature = "file-changes")]
#[derive(Debug, Error)]
#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
pub enum DirWalkError {
#[error("Failed to read {path}: {source}")]
ReadDir {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(transparent)]
OsError(#[from] std::io::Error),
}
#[cfg(feature = "pyo3")]
impl From<OutputVariableError> for PyErr {
fn from(e: OutputVariableError) -> Self {
match e {
OutputVariableError::NameIsEmpty
| OutputVariableError::NameStartsWithNumber(_)
| OutputVariableError::NameContainsNonPrintableCharacters(_)
| OutputVariableError::ValueContainsNonPrintableCharacters(_) => {
PyValueError::new_err(format!("{e:?}"))
}
OutputVariableError::UnsupportedPlatform => PyRuntimeError::new_err(format!("{e:?}")),
}
}
}
#[cfg(feature = "pyo3")]
impl From<DiffError> for PyErr {
fn from(e: DiffError) -> Self {
match e {
DiffError::RegExCompileFailed(_) => PyValueError::new_err(format!("{e:?}")),
}
}
}
#[cfg(feature = "pyo3")]
impl From<DirWalkError> for PyErr {
fn from(e: DirWalkError) -> Self {
PyOSError::new_err(format!("{e:?}"))
}
}
#[cfg(feature = "pyo3")]
impl From<RestClientError> for PyErr {
fn from(err: RestClientError) -> Self {
match err {
#[cfg(feature = "file-changes")]
RestClientError::DiffError(e) => e.into(),
RestClientError::MalformedEventInfo(_) => PyRuntimeError::new_err(format!("{err:?}")),
RestClientError::Request(e) => PyOSError::new_err(format!("{e:?}")),
RestClientError::RequestContext { task: _, source: _ }
| RestClientError::Io { task: _, source: _ }
| RestClientError::RateLimitNoReset
| RestClientError::RateLimitPrimary(_)
| RestClientError::RateLimitSecondary => PyOSError::new_err(format!("{err:?}")),
RestClientError::CannotCloneRequest
| RestClientError::InvalidHeaderValue(_)
| RestClientError::UnexpectedHeaderValue(_)
| RestClientError::HeaderParseInt(_)
| RestClientError::UrlParse(_)
| RestClientError::Json { task: _, source: _ }
| RestClientError::EnvVar { name: _, source: _ } => {
PyValueError::new_err(format!("{err:?}"))
}
#[cfg(feature = "file-changes")]
RestClientError::GitCommand(_) => PyValueError::new_err(format!("{err:?}")),
RestClientError::OutputVar(e) => e.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::RestClientError;
#[test]
fn no_added_req_ctx() {
let err = RestClientError::CannotCloneRequest;
assert!(matches!(
err.add_request_context("some task"),
RestClientError::CannotCloneRequest
));
}
}