1#[cfg(feature = "eventsource")]
2mod event_stream;
3pub use better_default::Default;
4pub use bon::bon;
5#[cfg(feature = "eventsource")]
6pub use event_stream::{EventStream, EventStreamError};
7pub use http::Method;
8use http::{StatusCode, header::RETRY_AFTER};
9use serde::de::DeserializeOwned;
10use serde_with::{
11 StringWithSeparator,
12 formats::{CommaSeparator, Separator, SpaceSeparator},
13};
14
15pub struct PipeSeparator;
17
18impl Separator for PipeSeparator {
19 #[inline]
20 fn separator() -> &'static str {
21 "|"
22 }
23}
24
25pub type StringWithCommaSeparator = StringWithSeparator<CommaSeparator, String>;
29
30pub type StringWithSpaceSeparator = StringWithSeparator<SpaceSeparator, String>;
34
35pub type StringWithPipeSeparator = StringWithSeparator<PipeSeparator, String>;
39
40#[derive(Debug, thiserror::Error)]
41pub enum DiagnosticsError {
42 #[cfg(feature = "reqwest")]
43 #[error(transparent)]
44 BodyReadError(#[from] reqwest::Error),
45
46 #[error("JSON deserialization error at path '{path}': {inner}")]
47 DeserializationError { path: String, inner: serde_json::Error },
48
49 #[cfg(feature = "quick-xml")]
50 #[error(transparent)]
51 XmlDeserializationError(#[from] quick_xml::DeError),
52}
53
54#[allow(async_fn_in_trait)]
55pub trait Diagnostics<T>
56where
57 T: serde::de::DeserializeOwned,
58{
59 async fn json_with_diagnostics(self) -> Result<T, DiagnosticsError>;
60
61 #[cfg(feature = "quick-xml")]
62 async fn xml_with_diagnostics(self) -> Result<T, DiagnosticsError>;
63}
64
65#[cfg(feature = "reqwest")]
66impl<T> Diagnostics<T> for reqwest::Response
67where
68 T: serde::de::DeserializeOwned,
69{
70 async fn json_with_diagnostics(self) -> Result<T, DiagnosticsError> {
71 let raw_body = self.text().await?;
72 let mut de = serde_json::Deserializer::from_str(&raw_body);
73 serde_path_to_error::deserialize(&mut de).map_err(|err| DiagnosticsError::DeserializationError {
74 path: err.path().to_string(),
75 inner: err.into_inner(),
76 })
77 }
78
79 #[cfg(feature = "quick-xml")]
80 async fn xml_with_diagnostics(self) -> Result<T, DiagnosticsError> {
81 let raw_body = self.bytes().await?;
82 Ok(quick_xml::de::from_reader(std::io::Cursor::new(raw_body))?)
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
87pub enum RateLimit {
88 #[default]
90 Exceeded,
91 TryAgainAfter(u32),
93}
94
95impl RateLimit {
96 #[must_use]
99 pub fn with_headers(headers: &http::HeaderMap) -> Self {
100 if let Some(retry_after) = headers.get(RETRY_AFTER)
101 && let Ok(seconds) = String::from_utf8_lossy(retry_after.as_bytes()).parse::<u32>()
102 {
103 return Self::TryAgainAfter(seconds);
104 }
105 Self::Exceeded
106 }
107}
108
109#[derive(Debug, Clone)]
110pub struct TooManyRequests<T: DeserializeOwned>(RateLimit, T);
111
112impl<T: DeserializeOwned> TooManyRequests<T> {
113 #[must_use]
114 pub fn is_too_many_requests(status: StatusCode) -> bool {
115 status == StatusCode::TOO_MANY_REQUESTS
116 }
117
118 #[must_use]
120 pub fn new(headers: &http::HeaderMap, inner: T) -> Self {
121 Self(RateLimit::with_headers(headers), inner)
122 }
123
124 pub fn rate_limit(&self) -> &RateLimit {
127 &self.0
128 }
129
130 #[must_use]
132 pub fn into_inner(self) -> T {
133 self.1
134 }
135}