#[cfg(feature = "eventsource")]
mod event_stream;
pub use better_default::Default;
pub use bon::bon;
#[cfg(feature = "eventsource")]
pub use event_stream::{EventStream, EventStreamError};
pub use http::Method;
use http::{StatusCode, header::RETRY_AFTER};
use serde::de::DeserializeOwned;
use serde_with::{
StringWithSeparator,
formats::{CommaSeparator, Separator, SpaceSeparator},
};
pub struct PipeSeparator;
impl Separator for PipeSeparator {
#[inline]
fn separator() -> &'static str {
"|"
}
}
pub type StringWithCommaSeparator = StringWithSeparator<CommaSeparator, String>;
pub type StringWithSpaceSeparator = StringWithSeparator<SpaceSeparator, String>;
pub type StringWithPipeSeparator = StringWithSeparator<PipeSeparator, String>;
#[derive(Debug, thiserror::Error)]
pub enum DiagnosticsError {
#[cfg(feature = "reqwest")]
#[error(transparent)]
BodyReadError(#[from] reqwest::Error),
#[error("JSON deserialization error at path '{path}': {inner}")]
DeserializationError { path: String, inner: serde_json::Error },
#[cfg(feature = "quick-xml")]
#[error(transparent)]
XmlDeserializationError(#[from] quick_xml::DeError),
}
#[allow(async_fn_in_trait)]
pub trait Diagnostics<T>
where
T: serde::de::DeserializeOwned,
{
async fn json_with_diagnostics(self) -> Result<T, DiagnosticsError>;
#[cfg(feature = "quick-xml")]
async fn xml_with_diagnostics(self) -> Result<T, DiagnosticsError>;
}
#[cfg(feature = "reqwest")]
impl<T> Diagnostics<T> for reqwest::Response
where
T: serde::de::DeserializeOwned,
{
async fn json_with_diagnostics(self) -> Result<T, DiagnosticsError> {
let raw_body = self.text().await?;
let mut de = serde_json::Deserializer::from_str(&raw_body);
serde_path_to_error::deserialize(&mut de).map_err(|err| DiagnosticsError::DeserializationError {
path: err.path().to_string(),
inner: err.into_inner(),
})
}
#[cfg(feature = "quick-xml")]
async fn xml_with_diagnostics(self) -> Result<T, DiagnosticsError> {
let raw_body = self.bytes().await?;
Ok(quick_xml::de::from_reader(std::io::Cursor::new(raw_body))?)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub enum RateLimit {
#[default]
Exceeded,
TryAgainAfter(u32),
}
impl RateLimit {
#[must_use]
pub fn with_headers(headers: &http::HeaderMap) -> Self {
if let Some(retry_after) = headers.get(RETRY_AFTER)
&& let Ok(seconds) = String::from_utf8_lossy(retry_after.as_bytes()).parse::<u32>()
{
return Self::TryAgainAfter(seconds);
}
Self::Exceeded
}
}
#[derive(Debug, Clone)]
pub struct TooManyRequests<T: DeserializeOwned>(RateLimit, T);
impl<T: DeserializeOwned> TooManyRequests<T> {
#[must_use]
pub fn is_too_many_requests(status: StatusCode) -> bool {
status == StatusCode::TOO_MANY_REQUESTS
}
#[must_use]
pub fn new(headers: &http::HeaderMap, inner: T) -> Self {
Self(RateLimit::with_headers(headers), inner)
}
pub fn rate_limit(&self) -> &RateLimit {
&self.0
}
#[must_use]
pub fn into_inner(self) -> T {
self.1
}
}