Skip to main content

oas3_gen_support/
lib.rs

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
15/// Pipe separator for `OpenAPI` pipeDelimited style
16pub struct PipeSeparator;
17
18impl Separator for PipeSeparator {
19  #[inline]
20  fn separator() -> &'static str {
21    "|"
22  }
23}
24
25/// De/Serialize a delimited collection using [`Display`] and [`FromStr`] implementation
26///
27/// An empty string deserializes as an empty collection.
28pub type StringWithCommaSeparator = StringWithSeparator<CommaSeparator, String>;
29
30/// De/Serialize a delimited collection using [`Display`] and [`FromStr`] implementation
31///
32/// An empty string deserializes as an empty collection.
33pub type StringWithSpaceSeparator = StringWithSeparator<SpaceSeparator, String>;
34
35/// De/Serialize a delimited collection using [`Display`] and [`FromStr`] implementation
36///
37/// An empty string deserializes as an empty collection.
38pub 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  /// The client has sent too many requests in a given amount of time.
89  #[default]
90  Exceeded,
91  /// The client should try the request again after the specified number of seconds.
92  TryAgainAfter(u32),
93}
94
95impl RateLimit {
96  /// If the `Retry-After` header is present and valid, it will be used to create a `TryAgainAfter` variant.
97  /// Otherwise, it will return `Exceeded`.
98  #[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  /// Create a new `TooManyRequests` instance by extracting rate limit information from headers
119  #[must_use]
120  pub fn new(headers: &http::HeaderMap, inner: T) -> Self {
121    Self(RateLimit::with_headers(headers), inner)
122  }
123
124  /// If the `Retry-After` header is present and valid, it will be used to create a `TryAgainAfter` variant.
125  /// Otherwise, it will return `Exceeded`.
126  pub fn rate_limit(&self) -> &RateLimit {
127    &self.0
128  }
129
130  /// Consume the `TooManyRequests` and return the inner value
131  #[must_use]
132  pub fn into_inner(self) -> T {
133    self.1
134  }
135}