1use std::sync::{Arc, Mutex};
4use std::time::Duration;
5
6use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
7
8use crate::client::{
9 default_backoff, BackoffFn, Client, ClientInner, Overrides, TokenCache, DEFAULT_BASE_URL,
10};
11use crate::error::{Error, Result};
12use crate::token::decode_token;
13
14pub(crate) const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
15pub(crate) const DEFAULT_MAX_RETRIES: u32 = 2;
16
17#[derive(Debug, Clone, Default)]
19pub struct TokenOptions {
20 pub permissions: Option<Vec<String>>,
22 pub roles: Option<Vec<String>>,
24 pub version: Option<i64>,
26 pub expires_in: Option<Duration>,
28}
29
30#[derive(Clone)]
32pub(crate) struct Config {
33 pub(crate) api_key: Option<String>,
34 pub(crate) secret: Option<String>,
35 pub(crate) max_retries: u32,
36 pub(crate) timeout: Duration,
38 pub(crate) headers: HeaderMap,
39 pub(crate) token_options: TokenOptions,
40}
41
42#[derive(Default)]
59pub struct ClientBuilder {
60 api_key: Option<String>,
61 secret: Option<String>,
62 base_url: Option<String>,
63 token: Option<String>,
64 http_client: Option<reqwest::Client>,
65 max_retries: Option<u32>,
66 timeout: Option<Duration>,
67 headers: Vec<(String, String)>,
68 token_options: TokenOptions,
69 backoff: Option<BackoffFn>,
70}
71
72impl ClientBuilder {
73 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
80 self.api_key = Some(api_key.into());
81 self
82 }
83
84 pub fn secret(mut self, secret: impl Into<String>) -> Self {
86 self.secret = Some(secret.into());
87 self
88 }
89
90 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
93 self.base_url = Some(base_url.into());
94 self
95 }
96
97 pub fn token(mut self, token: impl Into<String>) -> Self {
100 self.token = Some(token.into());
101 self
102 }
103
104 pub fn http_client(mut self, http_client: reqwest::Client) -> Self {
107 self.http_client = Some(http_client);
108 self
109 }
110
111 pub fn max_retries(mut self, max_retries: u32) -> Self {
114 self.max_retries = Some(max_retries);
115 self
116 }
117
118 pub fn request_timeout(mut self, timeout: Duration) -> Self {
124 self.timeout = Some(timeout);
125 self
126 }
127
128 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
130 self.headers.push((name.into(), value.into()));
131 self
132 }
133
134 pub fn token_options(mut self, token_options: TokenOptions) -> Self {
136 self.token_options = token_options;
137 self
138 }
139
140 pub fn backoff(mut self, backoff: impl Fn(u32) -> Duration + Send + Sync + 'static) -> Self {
145 self.backoff = Some(Arc::new(backoff));
146 self
147 }
148
149 pub fn build(self) -> Result<Client> {
152 let api_key = self.api_key.or_else(|| env_var("VIDEOSDK_API_KEY"));
153 let secret = self.secret.or_else(|| env_var("VIDEOSDK_SECRET"));
154 let base_url = self
155 .base_url
156 .or_else(|| env_var("VIDEOSDK_API_ENDPOINT"))
157 .unwrap_or_else(|| DEFAULT_BASE_URL.to_string());
158
159 if self.token.is_none() && (api_key.is_none() || secret.is_none()) {
160 return Err(Error::config(
161 "Client::builder() requires either a token, or both an API key and secret \
162 (via .api_key()/.secret(), or the VIDEOSDK_API_KEY / VIDEOSDK_SECRET env vars)",
163 ));
164 }
165
166 let mut headers = HeaderMap::new();
167 for (name, value) in self.headers {
168 let name = HeaderName::try_from(name.as_str())
169 .map_err(|_| Error::config(format!("invalid header name: {name:?}")))?;
170 let value = HeaderValue::try_from(value.as_str())
171 .map_err(|_| Error::config(format!("invalid value for header {name:?}")))?;
172 headers.insert(name, value);
173 }
174
175 let http = match self.http_client {
176 Some(http) => http,
177 None => reqwest::Client::builder()
178 .build()
179 .map_err(|e| Error::config(format!("failed to build the HTTP client: {e}")))?,
180 };
181
182 let can_refresh = api_key.is_some() && secret.is_some();
183
184 let mut cache = TokenCache::default();
186 if let Some(token) = self.token {
187 cache.expires_at = decode_token(&token).ok().and_then(|c| c.expires_at);
188 cache.token = Some(token);
189 }
190
191 let config = Config {
192 api_key,
193 secret,
194 max_retries: self.max_retries.unwrap_or(DEFAULT_MAX_RETRIES),
195 timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
196 headers,
197 token_options: self.token_options,
198 };
199
200 Ok(Client::from_parts(
201 Arc::new(ClientInner {
202 config,
203 http,
204 base_url: base_url.trim_end_matches('/').to_string(),
205 can_refresh,
206 token: Mutex::new(cache),
207 backoff: self.backoff.unwrap_or_else(|| Arc::new(default_backoff)),
208 }),
209 Overrides::default(),
210 ))
211 }
212}
213
214fn env_var(name: &str) -> Option<String> {
215 std::env::var(name).ok().filter(|v| !v.is_empty())
216}