use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use futures_util::StreamExt;
use crate::error::{Error, Result};
#[derive(Clone)]
pub struct Client {
api: crate::Client,
runtime: Arc<tokio::runtime::Runtime>,
}
impl std::fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("blocking::Client")
.field("api", &self.api)
.finish_non_exhaustive()
}
}
impl Client {
pub fn new(base_url: impl AsRef<str>) -> Result<Self> {
Self::builder(base_url).build()
}
pub fn builder(base_url: impl AsRef<str>) -> ClientBuilder {
ClientBuilder {
inner: crate::Client::builder(base_url),
worker_threads: None,
}
}
pub fn api(&self) -> &crate::Client {
&self.api
}
pub fn base_url(&self) -> &str {
self.api.base_url()
}
pub fn call<'a, F, Fut, T>(&'a self, request: F) -> Result<T>
where
F: FnOnce(&'a crate::Client) -> Fut,
Fut: Future<Output = Result<T>> + 'a,
{
self.runtime.block_on(request(&self.api))
}
pub fn block_on<Fut: Future>(&self, future: Fut) -> Fut::Output {
self.runtime.block_on(future)
}
pub fn collect<S, T>(&self, stream: S) -> Result<Vec<T>>
where
S: futures_core::Stream<Item = Result<T>>,
{
self.runtime.block_on(async move {
let mut stream = std::pin::pin!(stream);
let mut items = Vec::new();
while let Some(item) = stream.next().await {
items.push(item?);
}
Ok(items)
})
}
pub fn for_each<S, T, F>(&self, stream: S, mut handler: F) -> Result<()>
where
S: futures_core::Stream<Item = Result<T>>,
F: FnMut(T) -> bool,
{
self.runtime.block_on(async move {
let mut stream = std::pin::pin!(stream);
while let Some(item) = stream.next().await {
if !handler(item?) {
break;
}
}
Ok(())
})
}
}
pub struct ClientBuilder {
inner: crate::ClientBuilder,
worker_threads: Option<usize>,
}
impl ClientBuilder {
pub fn worker_threads(mut self, threads: usize) -> Self {
self.worker_threads = Some(threads);
self
}
pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
self.inner = self.inner.api_key(api_key);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.inner = self.inner.timeout(timeout);
self
}
pub fn gpu(mut self, gpu: impl Into<String>) -> Self {
self.inner = self.inner.gpu(gpu);
self
}
pub fn options(mut self, options: serde_json::Value) -> Self {
self.inner = self.inner.options(options);
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.inner = self.inner.max_connections(max);
self
}
pub fn max_concurrency(mut self, max: usize) -> Self {
self.inner = self.inner.max_concurrency(max);
self
}
pub fn control_plane_url(mut self, url: impl Into<String>) -> Self {
self.inner = self.inner.control_plane_url(url);
self
}
pub fn org(mut self, org: impl Into<String>) -> Self {
self.inner = self.inner.org(org);
self
}
pub fn base_url_headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
self.inner = self.inner.base_url_headers(headers);
self
}
pub fn wait_for_capacity(mut self, wait: bool) -> Self {
self.inner = self.inner.wait_for_capacity(wait);
self
}
pub fn provision_timeout(mut self, timeout: Duration) -> Self {
self.inner = self.inner.provision_timeout(timeout);
self
}
pub fn max_oom_retries(mut self, retries: u32) -> Self {
self.inner = self.inner.max_oom_retries(retries);
self
}
pub fn build(self) -> Result<Client> {
let mut runtime = tokio::runtime::Builder::new_multi_thread();
runtime
.worker_threads(self.worker_threads.unwrap_or(1))
.enable_all();
let runtime = runtime.build().map_err(|err| {
Error::invalid(format!("could not start the blocking runtime: {err}"))
})?;
let api = runtime.block_on(async { self.inner.build() })?;
Ok(Client {
api,
runtime: Arc::new(runtime),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Item;
#[test]
fn builder_options_reach_the_async_client() {
let client = Client::builder("https://sie.example.com/")
.timeout(Duration::from_secs(5))
.max_oom_retries(0)
.wait_for_capacity(false)
.build()
.unwrap();
assert_eq!(client.base_url(), "https://sie.example.com/");
assert!(!client.api().default_options().wait_for_capacity);
assert_eq!(client.api().default_options().max_oom_retries, 0);
}
#[test]
fn call_runs_a_request_and_returns_its_error() {
let client = Client::builder("http://127.0.0.1:1")
.timeout(Duration::from_millis(200))
.wait_for_capacity(false)
.build()
.unwrap();
let result = client.call(|sie| sie.encode("m", [Item::text("hi")]).send_one());
assert!(
matches!(result, Err(Error::Connection { .. })),
"{result:?}"
);
}
#[test]
fn client_side_validation_still_applies() {
let client = Client::new("https://sie.invalid").unwrap();
let result = client.call(|sie| sie.encode("m", Vec::new()).send());
assert!(
matches!(result, Err(Error::InvalidRequest(_))),
"{result:?}"
);
}
#[test]
fn block_on_drives_arbitrary_futures() {
let client = Client::new("https://sie.invalid").unwrap();
assert_eq!(client.block_on(async { 1 + 1 }), 2);
}
#[test]
fn collect_stops_at_the_first_error() {
let client = Client::new("https://sie.invalid").unwrap();
let stream =
futures_util::stream::iter(vec![Ok(1), Err(Error::invalid("stop here")), Ok(3)]);
let result: Result<Vec<i32>> = client.collect(stream);
assert!(result.is_err());
let ok = futures_util::stream::iter(vec![Ok(1), Ok(2)]);
assert_eq!(client.collect::<_, i32>(ok).unwrap(), vec![1, 2]);
}
#[test]
fn for_each_can_stop_early() {
let client = Client::new("https://sie.invalid").unwrap();
let stream = futures_util::stream::iter(vec![Ok(1), Ok(2), Ok(3)]);
let mut seen = Vec::new();
client
.for_each(stream, |value: i32| {
seen.push(value);
value < 2
})
.unwrap();
assert_eq!(seen, vec![1, 2]);
}
}