use std::collections::HashMap;
use std::fmt;
use std::time::Duration;
use serde::Deserialize;
use crate::client::{BoxError, HttpResponse};
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Api(ApiError),
Transport(BoxError),
Json(serde_json::Error),
InsecureBaseUrl,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ApiError {
pub status: u16,
pub code: String,
pub message: String,
pub fields: HashMap<String, Vec<String>>,
pub retry_after: Option<Duration>,
}
#[derive(Deserialize, Default)]
struct ErrorBody {
error: Option<String>,
message: Option<String>,
fields: Option<HashMap<String, Vec<String>>>,
}
impl ApiError {
pub(crate) fn from_response(response: &HttpResponse) -> Self {
let body: ErrorBody = serde_json::from_slice(&response.body).unwrap_or_default();
Self {
status: response.status,
code: body.error.unwrap_or_default(),
message: body.message.unwrap_or_default(),
fields: body.fields.unwrap_or_default(),
retry_after: response
.header("retry-after")
.and_then(|value| value.trim().parse::<u64>().ok())
.map(Duration::from_secs),
}
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.message.is_empty() {
write!(f, "alpr: {} {}", self.status, self.code)
} else {
write!(f, "alpr: {} {}: {}", self.status, self.code, self.message)
}
}
}
impl std::error::Error for ApiError {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Api(error) => error.fmt(f),
Error::Transport(error) => write!(f, "alpr: {error}"),
Error::Json(error) => write!(f, "alpr: json: {error}"),
Error::InsecureBaseUrl => f.write_str("alpr: the base URL must be https (http is accepted only for localhost, 127.0.0.0/8 and ::1), so the API key is never sent in clear text"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Api(error) => Some(error),
Error::Transport(error) => Some(error.as_ref()),
Error::Json(error) => Some(error),
Error::InsecureBaseUrl => None,
}
}
}
impl From<ApiError> for Error {
fn from(error: ApiError) -> Self {
Error::Api(error)
}
}
impl Error {
pub fn api(&self) -> Option<&ApiError> {
match self {
Error::Api(error) => Some(error),
_ => None,
}
}
}