use std::{error::Error as StdError, fmt};
use serde::Deserialize;
use serde_json::{Map, Value};
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Http(HttpError),
Server(ServerError),
Parse(ParseError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Http(error) => error.fmt(f),
Error::Server(error) => error.fmt(f),
Error::Parse(error) => error.fmt(f),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::Http(error) => Some(error),
Error::Server(error) => Some(error),
Error::Parse(error) => Some(error),
}
}
}
#[derive(Debug)]
pub struct HttpError(pub(crate) HttpErrorKind);
#[derive(Debug)]
pub(crate) enum HttpErrorKind {
#[cfg(feature = "reqwest")]
Reqwest(reqwest::Error),
}
impl fmt::Display for HttpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _ = f;
match self.0 {
#[cfg(feature = "reqwest")]
HttpErrorKind::Reqwest(ref error) => write!(f, "http request failed: {error}"),
}
}
}
impl StdError for HttpError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self.0 {
#[cfg(feature = "reqwest")]
HttpErrorKind::Reqwest(ref error) => Some(error),
}
}
}
#[derive(Debug)]
pub struct ServerError {
code: ErrorCode,
description: Option<String>,
uri: Option<String>,
status: u16,
extra: Map<String, Value>,
}
impl ServerError {
pub fn code(&self) -> &ErrorCode {
&self.code
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn uri(&self) -> Option<&str> {
self.uri.as_deref()
}
pub fn status(&self) -> u16 {
self.status
}
pub fn extra_field<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
let value = self.extra.get(key)?;
serde_json::from_value(value.clone()).ok()
}
}
impl fmt::Display for ServerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"server returned error \"{}\" (status {})",
self.code, self.status
)?;
if let Some(description) = &self.description {
write!(f, ": {description}")?;
}
Ok(())
}
}
impl StdError for ServerError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorCode {
InvalidRequest,
InvalidClient,
InvalidGrant,
UnauthorizedClient,
UnsupportedGrantType,
InvalidScope,
Other(String),
}
impl ErrorCode {
pub fn as_str(&self) -> &str {
match self {
ErrorCode::InvalidRequest => "invalid_request",
ErrorCode::InvalidClient => "invalid_client",
ErrorCode::InvalidGrant => "invalid_grant",
ErrorCode::UnauthorizedClient => "unauthorized_client",
ErrorCode::UnsupportedGrantType => "unsupported_grant_type",
ErrorCode::InvalidScope => "invalid_scope",
ErrorCode::Other(code) => code,
}
}
fn from_wire(code: String) -> Self {
match code.as_str() {
"invalid_request" => ErrorCode::InvalidRequest,
"invalid_client" => ErrorCode::InvalidClient,
"invalid_grant" => ErrorCode::InvalidGrant,
"unauthorized_client" => ErrorCode::UnauthorizedClient,
"unsupported_grant_type" => ErrorCode::UnsupportedGrantType,
"invalid_scope" => ErrorCode::InvalidScope,
_ => ErrorCode::Other(code),
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Deserialize)]
pub(crate) struct ServerErrorWire {
error: String,
#[serde(default)]
error_description: Option<String>,
#[serde(default)]
error_uri: Option<String>,
#[serde(flatten)]
extra: Map<String, Value>,
}
impl ServerErrorWire {
pub(crate) fn into_server_error(self, status: u16) -> ServerError {
ServerError {
code: ErrorCode::from_wire(self.error),
description: self.error_description,
uri: self.error_uri,
status,
extra: self.extra,
}
}
}
pub struct ParseError {
pub(crate) url: String,
pub(crate) status: u16,
pub(crate) content_type: Option<String>,
pub(crate) body: Vec<u8>,
pub(crate) source: serde_json::Error,
}
impl ParseError {
pub fn url(&self) -> &str {
&self.url
}
pub fn status(&self) -> u16 {
self.status
}
pub fn content_type(&self) -> Option<&str> {
self.content_type.as_deref()
}
pub fn body(&self) -> &[u8] {
&self.body
}
}
impl fmt::Debug for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ParseError")
.field("url", &self.url)
.field("status", &self.status)
.field("content_type", &self.content_type)
.field(
"body",
&format_args!("[redacted, {} bytes]", self.body.len()),
)
.field("source", &self.source)
.finish()
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"could not parse response from \"{}\" (status {}, content-type {}, {} bytes): {}",
self.url,
self.status,
self.content_type.as_deref().unwrap_or("unknown"),
self.body.len(),
self.source
)
}
}
impl StdError for ParseError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&self.source)
}
}