1use governor::clock::DefaultClock;
2use governor::middleware::NoOpMiddleware;
3use governor::state::{InMemoryState, NotKeyed};
4use governor::{Jitter, Quota, RateLimiter};
5use reqwest::{Client, RequestBuilder, Response, StatusCode};
6use serde::de::DeserializeOwned;
7use std::num::NonZeroU32;
8use std::sync::Arc;
9use std::time::Duration;
10
11use crate::error::{ApiError, ApiResult};
12use crate::types::ErrorResponse;
13
14const DEFAULT_BASE_URL: &str = "https://app.usesend.com/api";
15const DEFAULT_RATE_LIMIT: u32 = 9;
16
17type DirectRateLimiter = RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>;
18
19pub struct Config {
21 pub(crate) client: Client,
22 pub(crate) base_url: String,
23 pub(crate) api_key: String,
24 pub(crate) limiter: Arc<DirectRateLimiter>,
25}
26
27impl std::fmt::Debug for Config {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("Config")
30 .field("base_url", &self.base_url)
31 .field("api_key", &"[REDACTED]")
32 .finish()
33 }
34}
35
36impl Config {
37 pub fn new(api_key: impl Into<String>) -> Self {
38 Self {
39 client: Client::new(),
40 base_url: DEFAULT_BASE_URL.to_string(),
41 api_key: api_key.into(),
42 limiter: Arc::new(Self::build_limiter(DEFAULT_RATE_LIMIT)),
43 }
44 }
45
46 pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
47 self.base_url = base_url.into();
48 self
49 }
50
51 pub fn with_client(mut self, client: Client) -> Self {
52 self.client = client;
53 self
54 }
55
56 pub fn with_rate_limit(mut self, per_second: u32) -> Self {
58 self.limiter = Arc::new(Self::build_limiter(per_second));
59 self
60 }
61
62 fn build_limiter(per_second: u32) -> DirectRateLimiter {
63 let quota = Quota::with_period(Duration::from_millis(1100))
64 .expect("valid quota")
65 .allow_burst(NonZeroU32::new(per_second).expect("rate limit must be > 0"));
66 RateLimiter::direct(quota)
67 }
68
69 pub(crate) fn url(&self, path: &str) -> String {
70 format!("{}{}", self.base_url, path)
71 }
72
73 pub(crate) fn auth(&self, req: RequestBuilder) -> RequestBuilder {
74 req.bearer_auth(&self.api_key)
75 }
76
77 pub(crate) async fn send_and_parse<T: DeserializeOwned>(
78 &self,
79 req: RequestBuilder,
80 ) -> ApiResult<T> {
81 let jitter = Jitter::new(Duration::from_millis(10), Duration::from_millis(50));
82 self.limiter.until_ready_with_jitter(jitter).await;
83
84 let resp: Response = req.send().await?;
85 handle_response(resp).await
86 }
87}
88
89pub(crate) async fn handle_response<T: DeserializeOwned>(resp: Response) -> ApiResult<T> {
90 let status = resp.status();
91 if status.is_success() {
92 let body = resp.text().await?;
93 serde_json::from_str::<T>(&body).map_err(|source| ApiError::Deserialize {
94 status,
95 body,
96 source,
97 })
98 } else if status == StatusCode::CONFLICT {
99 let text = resp.text().await.unwrap_or_default();
100 Err(ApiError::Conflict { message: text })
101 } else if status == StatusCode::TOO_MANY_REQUESTS {
102 let retry_after = resp
103 .headers()
104 .get("ratelimit-reset")
105 .and_then(|v| v.to_str().ok())
106 .and_then(|v| v.parse::<u64>().ok());
107 Err(ApiError::RateLimit { retry_after })
108 } else {
109 let text = resp.text().await.unwrap_or_default();
110 match serde_json::from_str::<ErrorResponse>(&text) {
111 Ok(body) => Err(ApiError::Api { status, body }),
112 Err(_) => Err(ApiError::Unexpected { status, text }),
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
119pub(crate) struct SharedConfig(pub(crate) Arc<Config>);
120
121impl SharedConfig {
122 pub(crate) fn new(config: Config) -> Self {
123 Self(Arc::new(config))
124 }
125}
126
127impl std::ops::Deref for SharedConfig {
128 type Target = Config;
129 fn deref(&self) -> &Config {
130 &self.0
131 }
132}