pub const E_AUTH_MISSING: i64 = 893301;
pub const E_QR_TIMEOUT: i64 = 893302;
pub const E_CRYPTO: i64 = 893303;
pub const E_STORAGE: i64 = 893304;
pub const E_OTHER: i64 = 893999;
#[derive(Debug)]
pub enum AuthError {
MissingCredentials(String),
QrTimeout,
Crypto(String),
Storage(String),
Transport(wecomx_transport::Error),
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl AuthError {
#[must_use]
pub fn code(&self) -> i64 {
match self {
AuthError::Transport(inner) => inner.code(),
AuthError::MissingCredentials(_) => E_AUTH_MISSING,
AuthError::QrTimeout => E_QR_TIMEOUT,
AuthError::Crypto(_) => E_CRYPTO,
AuthError::Storage(_) => E_STORAGE,
AuthError::Other(_) => E_OTHER,
}
}
#[must_use]
pub fn message(&self) -> String {
match self {
AuthError::Transport(inner) => inner.to_string(),
AuthError::MissingCredentials(message)
| AuthError::Crypto(message)
| AuthError::Storage(message) => message.clone(),
AuthError::QrTimeout => "扫码超时(5 分钟),请重试".to_string(),
AuthError::Other(e) => e.to_string(),
}
}
}
impl std::fmt::Display for AuthError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let code = self.code();
match self {
AuthError::MissingCredentials(msg) => write!(f, "AuthError: {msg} [code={code}]"),
AuthError::QrTimeout => write!(f, "QrTimeout: {} [code={code}]", self.message()),
AuthError::Crypto(msg) => write!(f, "CryptoError: {msg} [code={code}]"),
AuthError::Storage(msg) => write!(f, "StorageError: {msg} [code={code}]"),
AuthError::Transport(inner) => write!(f, "{inner}"),
AuthError::Other(e) => write!(f, "UnknownError: {e} [code={code}]"),
}
}
}
impl std::error::Error for AuthError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
AuthError::Transport(inner) => Some(inner),
AuthError::Other(inner) => Some(inner.as_ref()),
_ => None,
}
}
}
impl From<wecomx_transport::Error> for AuthError {
fn from(e: wecomx_transport::Error) -> Self {
AuthError::Transport(e)
}
}
impl From<AuthError> for wecomx_transport::Error {
fn from(e: AuthError) -> Self {
match e {
AuthError::Transport(inner) => inner,
other => wecomx_transport::Error::Other(Box::new(other)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn code_maps_each_variant() {
assert_eq!(
AuthError::MissingCredentials("x".into()).code(),
E_AUTH_MISSING
);
assert_eq!(AuthError::QrTimeout.code(), E_QR_TIMEOUT);
assert_eq!(AuthError::Crypto("x".into()).code(), E_CRYPTO);
assert_eq!(AuthError::Storage("x".into()).code(), E_STORAGE);
assert_eq!(AuthError::Other("x".into()).code(), E_OTHER);
}
#[test]
fn code_transport_delegates() {
let e = AuthError::Transport(wecomx_transport::Error::Api {
message: "invalid".into(),
action: "/x".into(),
code: Some(853000),
body: Box::new(serde_json::Value::Null),
});
assert_eq!(e.code(), 853000);
}
#[test]
fn transport_error_roundtrip() {
let api = wecomx_transport::Error::Api {
message: "expired".into(),
action: "/x".into(),
code: Some(853004),
body: Box::new(serde_json::Value::Null),
};
let wrapped = AuthError::from(api);
assert!(matches!(wrapped, AuthError::Transport(_)));
let back: wecomx_transport::Error = wrapped.into();
assert!(matches!(
back,
wecomx_transport::Error::Api {
code: Some(853004),
..
}
));
}
#[test]
fn local_variant_boxes_into_transport_other() {
let e: wecomx_transport::Error = AuthError::MissingCredentials("need login".into()).into();
match e {
wecomx_transport::Error::Other(boxed) => {
let down = boxed.downcast_ref::<AuthError>();
assert!(
down.is_some_and(|e| matches!(e, AuthError::MissingCredentials(_))),
"expected AuthError::MissingCredentials"
);
}
other => panic!("expected Other, got {other:?}"),
}
}
}