1use std::future::Future;
31use std::sync::Arc;
32use std::time::Duration;
33
34use futures_util::StreamExt;
35
36use crate::error::{Error, Result};
37
38#[derive(Clone)]
42pub struct Client {
43 api: crate::Client,
44 runtime: Arc<tokio::runtime::Runtime>,
45}
46
47impl std::fmt::Debug for Client {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("blocking::Client")
50 .field("api", &self.api)
51 .finish_non_exhaustive()
52 }
53}
54
55impl Client {
56 pub fn new(base_url: impl AsRef<str>) -> Result<Self> {
58 Self::builder(base_url).build()
59 }
60
61 pub fn builder(base_url: impl AsRef<str>) -> ClientBuilder {
63 ClientBuilder {
64 inner: crate::Client::builder(base_url),
65 worker_threads: None,
66 }
67 }
68
69 pub fn api(&self) -> &crate::Client {
71 &self.api
72 }
73
74 pub fn base_url(&self) -> &str {
76 self.api.base_url()
77 }
78
79 pub fn call<'a, F, Fut, T>(&'a self, request: F) -> Result<T>
84 where
85 F: FnOnce(&'a crate::Client) -> Fut,
86 Fut: Future<Output = Result<T>> + 'a,
87 {
88 self.runtime.block_on(request(&self.api))
89 }
90
91 pub fn block_on<Fut: Future>(&self, future: Fut) -> Fut::Output {
95 self.runtime.block_on(future)
96 }
97
98 pub fn collect<S, T>(&self, stream: S) -> Result<Vec<T>>
103 where
104 S: futures_core::Stream<Item = Result<T>>,
105 {
106 self.runtime.block_on(async move {
107 let mut stream = std::pin::pin!(stream);
108 let mut items = Vec::new();
109 while let Some(item) = stream.next().await {
110 items.push(item?);
111 }
112 Ok(items)
113 })
114 }
115
116 pub fn for_each<S, T, F>(&self, stream: S, mut handler: F) -> Result<()>
120 where
121 S: futures_core::Stream<Item = Result<T>>,
122 F: FnMut(T) -> bool,
123 {
124 self.runtime.block_on(async move {
125 let mut stream = std::pin::pin!(stream);
126 while let Some(item) = stream.next().await {
127 if !handler(item?) {
128 break;
129 }
130 }
131 Ok(())
132 })
133 }
134}
135
136pub struct ClientBuilder {
138 inner: crate::ClientBuilder,
139 worker_threads: Option<usize>,
140}
141
142impl ClientBuilder {
143 pub fn worker_threads(mut self, threads: usize) -> Self {
148 self.worker_threads = Some(threads);
149 self
150 }
151
152 pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
154 self.inner = self.inner.api_key(api_key);
155 self
156 }
157
158 pub fn timeout(mut self, timeout: Duration) -> Self {
160 self.inner = self.inner.timeout(timeout);
161 self
162 }
163
164 pub fn gpu(mut self, gpu: impl Into<String>) -> Self {
166 self.inner = self.inner.gpu(gpu);
167 self
168 }
169
170 pub fn options(mut self, options: serde_json::Value) -> Self {
172 self.inner = self.inner.options(options);
173 self
174 }
175
176 pub fn max_connections(mut self, max: usize) -> Self {
178 self.inner = self.inner.max_connections(max);
179 self
180 }
181
182 pub fn max_concurrency(mut self, max: usize) -> Self {
184 self.inner = self.inner.max_concurrency(max);
185 self
186 }
187
188 pub fn control_plane_url(mut self, url: impl Into<String>) -> Self {
190 self.inner = self.inner.control_plane_url(url);
191 self
192 }
193
194 pub fn org(mut self, org: impl Into<String>) -> Self {
196 self.inner = self.inner.org(org);
197 self
198 }
199
200 pub fn base_url_headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
202 self.inner = self.inner.base_url_headers(headers);
203 self
204 }
205
206 pub fn wait_for_capacity(mut self, wait: bool) -> Self {
208 self.inner = self.inner.wait_for_capacity(wait);
209 self
210 }
211
212 pub fn provision_timeout(mut self, timeout: Duration) -> Self {
214 self.inner = self.inner.provision_timeout(timeout);
215 self
216 }
217
218 pub fn max_oom_retries(mut self, retries: u32) -> Self {
220 self.inner = self.inner.max_oom_retries(retries);
221 self
222 }
223
224 pub fn build(self) -> Result<Client> {
226 let mut runtime = tokio::runtime::Builder::new_multi_thread();
227 runtime
228 .worker_threads(self.worker_threads.unwrap_or(1))
229 .enable_all();
230 let runtime = runtime.build().map_err(|err| {
231 Error::invalid(format!("could not start the blocking runtime: {err}"))
232 })?;
233
234 let api = runtime.block_on(async { self.inner.build() })?;
237 Ok(Client {
238 api,
239 runtime: Arc::new(runtime),
240 })
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::types::Item;
248
249 #[test]
250 fn builder_options_reach_the_async_client() {
251 let client = Client::builder("https://sie.example.com/")
252 .timeout(Duration::from_secs(5))
253 .max_oom_retries(0)
254 .wait_for_capacity(false)
255 .build()
256 .unwrap();
257 assert_eq!(client.base_url(), "https://sie.example.com/");
258 assert!(!client.api().default_options().wait_for_capacity);
259 assert_eq!(client.api().default_options().max_oom_retries, 0);
260 }
261
262 #[test]
263 fn call_runs_a_request_and_returns_its_error() {
264 let client = Client::builder("http://127.0.0.1:1")
265 .timeout(Duration::from_millis(200))
266 .wait_for_capacity(false)
267 .build()
268 .unwrap();
269 let result = client.call(|sie| sie.encode("m", [Item::text("hi")]).send_one());
270 assert!(
271 matches!(result, Err(Error::Connection { .. })),
272 "{result:?}"
273 );
274 }
275
276 #[test]
277 fn client_side_validation_still_applies() {
278 let client = Client::new("https://sie.invalid").unwrap();
279 let result = client.call(|sie| sie.encode("m", Vec::new()).send());
280 assert!(
281 matches!(result, Err(Error::InvalidRequest(_))),
282 "{result:?}"
283 );
284 }
285
286 #[test]
287 fn block_on_drives_arbitrary_futures() {
288 let client = Client::new("https://sie.invalid").unwrap();
289 assert_eq!(client.block_on(async { 1 + 1 }), 2);
290 }
291
292 #[test]
293 fn collect_stops_at_the_first_error() {
294 let client = Client::new("https://sie.invalid").unwrap();
295 let stream =
296 futures_util::stream::iter(vec![Ok(1), Err(Error::invalid("stop here")), Ok(3)]);
297 let result: Result<Vec<i32>> = client.collect(stream);
298 assert!(result.is_err());
299
300 let ok = futures_util::stream::iter(vec![Ok(1), Ok(2)]);
301 assert_eq!(client.collect::<_, i32>(ok).unwrap(), vec![1, 2]);
302 }
303
304 #[test]
305 fn for_each_can_stop_early() {
306 let client = Client::new("https://sie.invalid").unwrap();
307 let stream = futures_util::stream::iter(vec![Ok(1), Ok(2), Ok(3)]);
308 let mut seen = Vec::new();
309 client
310 .for_each(stream, |value: i32| {
311 seen.push(value);
312 value < 2
313 })
314 .unwrap();
315 assert_eq!(seen, vec![1, 2]);
316 }
317}