use std::collections::BTreeMap;
use std::env::VarError;
use std::num::ParseIntError;
use reqwest::StatusCode;
use reqwest::header::{HeaderMap, InvalidHeaderValue};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
use url::ParseError;
pub type Result<T> = std::result::Result<T, RekaError>;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("REKA_API_KEY is not set")]
MissingApiKey,
#[error("{name} is not set")]
MissingEnvVar { name: &'static str },
#[error("{name} contains invalid unicode")]
InvalidEnvVar {
name: &'static str,
#[source]
source: VarError,
},
#[error("api key must not be empty")]
EmptyApiKey,
#[error("invalid base URL for {field}: {value}")]
InvalidBaseUrl {
field: &'static str,
value: String,
#[source]
source: ParseError,
},
#[error("invalid integer in {name}: {value}")]
InvalidNumber {
name: &'static str,
value: String,
#[source]
source: ParseIntError,
},
#[error("api key is not a valid HTTP header value")]
InvalidApiKeyHeader(#[source] InvalidHeaderValue),
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ApiErrorResponse {
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub detail: Option<Value>,
#[serde(default)]
pub error: Option<Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Debug, Error)]
pub enum RekaError {
#[error(transparent)]
Config(#[from] ConfigError),
#[error("invalid request: {0}")]
InvalidRequest(String),
#[error("request transport failed")]
Transport(#[source] reqwest::Error),
#[error(transparent)]
HttpStatus(Box<HttpStatusError>),
#[error(transparent)]
Decode(Box<DecodeError>),
}
#[derive(Debug, Error)]
#[error("request to {path} failed with HTTP {status}")]
pub struct HttpStatusError {
pub status: StatusCode,
pub path: String,
pub body: String,
pub api_error: Option<ApiErrorResponse>,
pub headers: HeaderMap,
}
#[derive(Debug, Error)]
#[error("failed to decode JSON response from {path}")]
pub struct DecodeError {
pub path: String,
pub body: String,
#[source]
pub source: serde_json::Error,
}
impl RekaError {
pub(crate) fn http_status(
status: StatusCode,
path: impl Into<String>,
body: String,
api_error: Option<ApiErrorResponse>,
headers: HeaderMap,
) -> Self {
Self::HttpStatus(Box::new(HttpStatusError {
status,
path: path.into(),
body,
api_error,
headers,
}))
}
pub(crate) fn decode(path: impl Into<String>, body: String, source: serde_json::Error) -> Self {
Self::Decode(Box::new(DecodeError {
path: path.into(),
body,
source,
}))
}
}