use std::error::Error;
use std::fmt::{Debug, Display, Formatter, Result};
use std::sync::Arc;
type ArcError = Arc<dyn Error + Send + Sync>;
#[derive(Clone, Debug)]
pub struct CredentialsError {
is_transient: bool,
message: Option<String>,
source: Option<ArcError>,
}
impl CredentialsError {
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub fn from_source<T: Error + Send + Sync + 'static>(is_transient: bool, source: T) -> Self {
CredentialsError {
is_transient,
source: Some(Arc::new(source)),
message: None,
}
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub fn from_msg<T: Into<String>>(is_transient: bool, message: T) -> Self {
CredentialsError {
is_transient,
message: Some(message.into()),
source: None,
}
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub fn new<M, S>(is_transient: bool, message: M, source: S) -> Self
where
M: Into<String>,
S: std::error::Error + Send + Sync + 'static,
{
CredentialsError {
is_transient,
message: Some(message.into()),
source: Some(Arc::new(source)),
}
}
pub fn is_transient(&self) -> bool {
self.is_transient
}
}
impl std::error::Error for CredentialsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|arc| arc.as_ref() as &(dyn std::error::Error + 'static))
}
}
const TRANSIENT_MSG: &str = "but future attempts may succeed";
const PERMANENT_MSG: &str = "and future attempts will not succeed";
impl Display for CredentialsError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let msg = if self.is_transient {
TRANSIENT_MSG
} else {
PERMANENT_MSG
};
match &self.message {
None => write!(f, "cannot create auth headers {msg}"),
Some(m) => write!(f, "{m} {msg}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case(true)]
#[test_case(false)]
fn from_source(transient: bool) {
let source = wkt::TimestampError::OutOfRange;
let got = CredentialsError::from_source(transient, source);
assert_eq!(got.is_transient(), transient, "{got:?}");
assert!(
got.source()
.and_then(|e| e.downcast_ref::<wkt::TimestampError>())
.is_some(),
"{got:?}"
);
assert!(
got.to_string().contains("cannot create auth headers"),
"{got:?}"
);
}
#[test_case(true)]
#[test_case(false)]
fn from_str(transient: bool) {
let got = CredentialsError::from_msg(transient, "test-only");
assert_eq!(got.is_transient(), transient, "{got:?}");
assert!(got.source().is_none(), "{got:?}");
assert!(got.to_string().contains("test-only"), "{got}");
}
#[test_case(true)]
#[test_case(false)]
fn new(transient: bool) {
let source = wkt::TimestampError::OutOfRange;
let got = CredentialsError::new(transient, "additional information", source);
assert_eq!(got.is_transient(), transient, "{got:?}");
assert!(
got.source()
.and_then(|e| e.downcast_ref::<wkt::TimestampError>())
.is_some(),
"{got:?}"
);
assert!(
got.to_string().contains("additional information"),
"{got:?}"
);
}
#[test]
fn fmt() {
let e = CredentialsError::from_msg(true, "test-only-err-123");
let got = format!("{e}");
assert!(got.contains("test-only-err-123"), "{got}");
assert!(got.contains(TRANSIENT_MSG), "{got}");
let e = CredentialsError::from_msg(false, "test-only-err-123");
let got = format!("{e}");
assert!(got.contains("test-only-err-123"), "{got}");
assert!(got.contains(PERMANENT_MSG), "{got}");
}
}