use std::fmt;
#[derive(Debug, Clone)]
pub struct OAuthError {
pub code: OAuthErrorCode,
pub description: Option<String>,
}
impl OAuthError {
pub(crate) fn new(error: &str, description: Option<String>) -> Self {
Self {
code: OAuthErrorCode::parse(error),
description,
}
}
}
impl fmt::Display for OAuthError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "OAuth error: {}", self.code)?;
if let Some(description) = &self.description {
write!(f, " - {description}")?;
}
Ok(())
}
}
impl std::error::Error for OAuthError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OAuthErrorCode {
InvalidRequest,
InvalidGrant,
InvalidClient,
UnauthorizedClient,
UnsupportedGrantType,
InvalidScope,
AccessDenied,
Other(String),
}
impl OAuthErrorCode {
fn parse(error: &str) -> Self {
match error {
"invalid_request" => Self::InvalidRequest,
"invalid_grant" => Self::InvalidGrant,
"invalid_client" => Self::InvalidClient,
"unauthorized_client" => Self::UnauthorizedClient,
"unsupported_grant_type" => Self::UnsupportedGrantType,
"invalid_scope" => Self::InvalidScope,
"access_denied" => Self::AccessDenied,
other => Self::Other(other.to_owned()),
}
}
}
impl fmt::Display for OAuthErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidRequest => f.write_str("invalid_request"),
Self::InvalidGrant => f.write_str("invalid_grant"),
Self::InvalidClient => f.write_str("invalid_client"),
Self::UnauthorizedClient => f.write_str("unauthorized_client"),
Self::UnsupportedGrantType => f.write_str("unsupported_grant_type"),
Self::InvalidScope => f.write_str("invalid_scope"),
Self::AccessDenied => f.write_str("access_denied"),
Self::Other(code) => f.write_str(code),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_known_codes() {
assert_eq!(
OAuthErrorCode::parse("invalid_grant"),
OAuthErrorCode::InvalidGrant
);
assert_eq!(
OAuthErrorCode::parse("access_denied"),
OAuthErrorCode::AccessDenied
);
}
#[test]
fn preserves_unknown_codes() {
assert_eq!(
OAuthErrorCode::parse("some_new_code"),
OAuthErrorCode::Other("some_new_code".to_owned())
);
}
#[test]
fn display_round_trips_known_codes() {
assert_eq!(OAuthErrorCode::InvalidGrant.to_string(), "invalid_grant");
}
}