yupdates/
clients.rs

1//! Wrappers that allow you set up the configurations once and then make many calls
2//!
3//! There is an `async` client and a synchronous version of it which hides the need for you to
4//! set up an async runtime. See the top-level documentation of this library for examples of each.
5//!
6//! If you want control over the `base_url`, `token`, or `http_client`, you can instantiate the
7//! [AsyncYupdatesClient] and [sync::SyncYupdatesClient] structs directly.
8//!
9//! The HTTP client can be configured with many options, see the Reqwest library's documentation
10//! for [ClientBuilder](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html), and be
11//! sure to adjust the documentation version to match the right version of this dependency (see
12//! this library's `Cargo.toml`).
13use crate::api::{
14    new_items_all_with_args, new_items_with_args, ping_with_args, read_items_with_args,
15    NewInputItemsResponse, PingResponse, ReadOptions,
16};
17use crate::errors::Result;
18use crate::models::{FeedItem, InputItem};
19use crate::{api_token, env_or_default_url};
20
21// ─────────────────────────────────────────────────────────────────────────────────────────────────
22// ASYNC CLIENT
23// ─────────────────────────────────────────────────────────────────────────────────────────────────
24
25/// Create an [AsyncYupdatesClient] instance using the default configuration sources.
26pub fn new_async_client() -> Result<AsyncYupdatesClient> {
27    let base_url = env_or_default_url()?;
28    let http_client = reqwest::Client::new();
29    let token = api_token()?;
30    Ok(AsyncYupdatesClient {
31        base_url,
32        http_client,
33        token,
34    })
35}
36
37/// Create an [AsyncYupdatesClient] instance using the default configuration sources and
38/// a custom [reqwest::Client]
39pub fn new_async_client_with_http_client(
40    http_client: reqwest::Client,
41) -> Result<AsyncYupdatesClient> {
42    let base_url = env_or_default_url()?;
43    let token = api_token()?;
44    Ok(AsyncYupdatesClient {
45        base_url,
46        http_client,
47        token,
48    })
49}
50
51/// Wraps everything needed to make async calls to the API
52///
53/// Instantiate this struct directly if you want total control. See [new_async_client] impl for
54/// the default values.
55pub struct AsyncYupdatesClient {
56    pub base_url: String,
57    pub http_client: reqwest::Client,
58    pub token: String,
59}
60
61// Rust does not support async traits, but here we "implement" `crate::api::YupdatesV0`
62impl AsyncYupdatesClient {
63    /// See [crate::api::YupdatesV0::new_items]
64    pub async fn new_items(&self, items: &[InputItem]) -> Result<NewInputItemsResponse> {
65        new_items_with_args(items, &self.http_client, &self.base_url, &self.token).await
66    }
67
68    /// See [crate::api::YupdatesV0::new_items_all]
69    pub async fn new_items_all(&self, items: &[InputItem], sleep_ms: u64) -> Result<String> {
70        new_items_all_with_args(
71            items,
72            sleep_ms,
73            &self.http_client,
74            &self.base_url,
75            &self.token,
76        )
77        .await
78    }
79
80    /// See [crate::api::YupdatesV0::ping]
81    pub async fn ping(&self) -> Result<PingResponse> {
82        ping_with_args(&self.http_client, &self.base_url, &self.token).await
83    }
84
85    /// See [crate::api::YupdatesV0::ping_bool]
86    pub async fn ping_bool(&self) -> bool {
87        self.ping().await.is_ok()
88    }
89
90    /// See [crate::api::YupdatesV0::read_items]
91    pub async fn read_items<S>(&self, feed_id: S) -> Result<Vec<FeedItem>>
92    where
93        S: AsRef<str>,
94    {
95        read_items_with_args(
96            feed_id.as_ref(),
97            None,
98            &self.http_client,
99            &self.base_url,
100            &self.token,
101        )
102        .await
103    }
104
105    /// See [crate::api::YupdatesV0::read_items_with_options]
106    pub async fn read_items_with_options<S>(
107        &self,
108        feed_id: S,
109        options: &ReadOptions,
110    ) -> Result<Vec<FeedItem>>
111    where
112        S: AsRef<str>,
113    {
114        read_items_with_args(
115            feed_id.as_ref(),
116            Some(options),
117            &self.http_client,
118            &self.base_url,
119            &self.token,
120        )
121        .await
122    }
123}
124
125// ─────────────────────────────────────────────────────────────────────────────────────────────────
126// SYNC CLIENT
127// ─────────────────────────────────────────────────────────────────────────────────────────────────
128
129// In the future, we would like this to be optional: #[cfg(feature = "sync_client")]
130/// Alternative client that sets up and hides a [tokio::runtime::Runtime](https://docs.rs/tokio/latest/tokio/runtime/index.html)
131pub mod sync {
132    use crate::api::{NewInputItemsResponse, PingResponse, ReadOptions, YupdatesV0};
133    use crate::clients::{new_async_client, AsyncYupdatesClient};
134    use crate::errors::{Error, Result};
135    use crate::models::{FeedItem, InputItem};
136    use crate::Kind;
137    use tokio::runtime::Runtime;
138
139    /// Wraps everything needed to make sync calls to the API, encapsulating a Tokio runtime.
140    ///
141    /// This allows you to make one-off CLIs more easily. You can list just `yupdates` as a
142    /// dependency and write code like `new_sync_client()?.ping()`.
143    pub struct SyncYupdatesClient {
144        pub client: AsyncYupdatesClient,
145        pub rt: Runtime,
146    }
147
148    /// Create a [SyncYupdatesClient] instance using the default configuration sources.
149    pub fn new_sync_client() -> Result<SyncYupdatesClient> {
150        let rt = match Runtime::new() {
151            Ok(rt) => rt,
152            Err(e) => {
153                return Err(Error {
154                    kind: Kind::Config(format!("Could not create Tokio runtime: {}", e)),
155                })
156            }
157        };
158        Ok(SyncYupdatesClient {
159            client: new_async_client()?,
160            rt,
161        })
162    }
163
164    impl YupdatesV0 for SyncYupdatesClient {
165        fn new_items(&self, items: &[InputItem]) -> Result<NewInputItemsResponse> {
166            self.rt.block_on(self.client.new_items(items))
167        }
168
169        fn new_items_all(&self, items: &[InputItem], sleep_ms: u64) -> Result<String> {
170            self.rt.block_on(self.client.new_items_all(items, sleep_ms))
171        }
172
173        fn ping(&self) -> Result<PingResponse> {
174            self.rt.block_on(self.client.ping())
175        }
176
177        fn ping_bool(&self) -> bool {
178            self.rt.block_on(self.client.ping_bool())
179        }
180
181        fn read_items<S>(&self, feed_id: S) -> Result<Vec<FeedItem>>
182        where
183            S: AsRef<str>,
184        {
185            self.rt.block_on(self.client.read_items(feed_id))
186        }
187
188        fn read_items_with_options<S>(
189            &self,
190            feed_id: S,
191            options: &ReadOptions,
192        ) -> Result<Vec<FeedItem>>
193        where
194            S: AsRef<str>,
195        {
196            self.rt
197                .block_on(self.client.read_items_with_options(feed_id, options))
198        }
199    }
200}