use std::{fmt::Display, str::FromStr};
use serde::Deserialize;
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum NumericOrTextCode {
String(String),
Number(usize),
}
#[derive(Deserialize, Debug, Clone)]
pub struct ErrorResponse {
pub code: NumericOrTextCode,
#[serde(default)]
pub message: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error("API usage error: {0}")]
ApiError(ErrorResponse),
#[error(transparent)]
Other(#[from] anyhow::Error),
#[error("{0}")]
UrlParse(#[from] url::ParseError),
#[error("{0}")]
SerdeError(#[from] serde_json::Error),
}
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum ClientResponse<T> {
Error(ErrorResponse),
Success(T),
EmptySuccess,
}
pub type ClientResult<T> = Result<T, ClientError>;
impl<T> ClientResponse<T> {
pub(crate) fn into_client_result(self) -> ClientResult<Option<T>> {
match self {
ClientResponse::Error(e) => Err(e.into()),
ClientResponse::Success(t) => Ok(Some(t)),
ClientResponse::EmptySuccess => Ok(None),
}
}
pub fn is_err(&self) -> bool {
matches!(self, Self::Error(_))
}
}
impl<T> FromStr for ClientResponse<T>
where
T: serde::de::DeserializeOwned,
{
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Ok(ClientResponse::EmptySuccess);
}
serde_json::from_str(s)
}
}
impl From<ErrorResponse> for ClientError {
fn from(err: ErrorResponse) -> Self {
Self::ApiError(err)
}
}
impl Display for NumericOrTextCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(s) => f.write_str(s.to_string().as_ref()),
Self::Number(n) => f.write_str(n.to_string().as_ref()),
}
}
}
impl Display for ErrorResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"Code: {}, Message: \"{}\"",
self.code,
self.message.as_deref().unwrap_or(""),
))
}
}