instances_social/
lib.rs

1#![deny(missing_docs)]
2
3/*!
4Rust API client for the instances.social API
5
6```no_run
7use instances_social::Client;
8
9const TOKEN: &'static str = "...";
10
11fn main() {
12    let client = Client::new(TOKEN);
13    // all the API calls are available like this:
14    //
15    // client.instances().list()
16    // client.instances().sample()
17    // client.instances().search()
18    // client.instances().show()
19    // client.versions().list()
20    // client.versions().show()
21}
22```
23*/
24
25#[macro_use]
26extern crate serde_derive;
27
28use std::error::Error;
29
30use serde::{Deserialize, Serialize};
31use tap_reader::Tap;
32use url::{ParseError, Url};
33
34pub mod instances;
35pub mod versions;
36
37fn url(path: &str) -> Result<Url, ParseError> {
38    let url = format!("https://instances.social/api/1.0/{}", path);
39    Url::parse(&url)
40}
41
42/// Instances.social client
43///
44/// requires an API token
45pub struct Client {
46    token: String,
47    client: reqwest::Client,
48}
49
50impl Client {
51    /// Create a new client from a token
52    pub fn new(token: &str) -> Client {
53        Client {
54            token: token.into(),
55            client: reqwest::Client::new(),
56        }
57    }
58
59    fn token(&self) -> String {
60        format!("Bearer {}", self.token)
61    }
62
63    /// Access the /versions/* API calls
64    pub fn versions(&self) -> Versions {
65        Versions(self)
66    }
67
68    /// Access the /instances/* API calls
69    pub fn instances(&self) -> Instances {
70        Instances(self)
71    }
72
73    fn get_qs<S: Serialize>(&self, path: &str, data: S) -> Result<reqwest::RequestBuilder, Box<Error>> {
74        let mut url = url(path)?;
75        let qs = serde_qs::to_string(&data)?;
76        url.set_query(Some(qs.as_ref()));
77        Ok(self.client.get(url.as_str()).header(reqwest::header::AUTHORIZATION, &self.token()[..]))
78    }
79
80    fn send<D>(&self, req: reqwest::RequestBuilder) -> Result<D, Box<Error>>
81    where
82        for<'de> D: Deserialize<'de>,
83    {
84        let resp = req.send()?;
85        let mut reader = Tap::new(resp);
86        Ok(match serde_json::from_reader(&mut reader) {
87            Ok(r) => r,
88            Err(e) => {
89                let err = String::from_utf8_lossy(&reader.bytes);
90                eprintln!("error: {:?}\nsource: {}", e, err);
91                return Err(e.into());
92            }
93        })
94    }
95}
96
97/// Container for the /versions/* API calls
98///
99/// # List versions
100///
101/// ```no_run
102/// use instances_social::Client;
103///
104/// const TOKEN: &'static str = "...";
105///
106/// fn main() -> Result<(), Box<std::error::Error>> {
107///     let client = Client::new(TOKEN);
108///
109///     let result = client.versions()
110///         .list() // returns this builder
111///         .count(100) // sets the ?count=100 querystring param
112///         // ...etc
113///         .send()?; // actually sends the request
114///     Ok(())
115/// }
116/// ```
117///
118/// # Show version
119///
120/// ```no_run
121/// use instances_social::Client;
122///
123/// const TOKEN: &'static str = "...";
124///
125/// fn main() -> Result<(), Box<std::error::Error>> {
126///     let client = Client::new(TOKEN);
127///
128///     let result = client.versions()
129///         .show("2.7.0") // returns this builder
130///         // ...etc
131///         .send()?; // actually sends the request
132///     Ok(())
133/// }
134/// ```
135pub struct Versions<'a>(&'a Client);
136impl<'a> Versions<'a> {
137    /// Starts build a versions/list call
138    pub fn list(&self) -> versions::ListRequestBuilder {
139        versions::ListRequestBuilder::new(self.0)
140    }
141
142    /// Starts building a versions/show call
143    pub fn show(&self, name: &str) -> versions::ShowRequestBuilder {
144        versions::ShowRequestBuilder::new(self.0, name)
145    }
146}
147
148/// Container for the /instances/* API calls
149///
150/// # Sample instances
151///
152/// ```no_run
153/// use instances_social::Client;
154///
155/// const TOKEN: &'static str = "...";
156///
157/// fn main() -> Result<(), Box<std::error::Error>> {
158///     let client = Client::new(TOKEN);
159///
160///     let result = client.instances()
161///         .sample() // returns this builder
162///         .count(100) // sets the ?count=100 querystring param
163///         // ...etc
164///         .send()?; // actually sends the request
165///     Ok(())
166/// }
167/// ```
168///
169/// # Show instance
170///
171///
172/// ```no_run
173/// use instances_social::Client;
174///
175/// const TOKEN: &'static str = "...";
176///
177/// fn main() -> Result<(), Box<std::error::Error>> {
178///     let client = Client::new(TOKEN);
179///
180///     let result = client.instances()
181///         .show("mastodon.social") // returns this builder
182///         .send()?; // actually sends the request
183///     Ok(())
184/// }
185/// ```
186///
187/// # List instances
188///
189/// ```no_run
190/// use instances_social::Client;
191///
192/// const TOKEN: &'static str = "...";
193///
194/// fn main() -> Result<(), Box<std::error::Error>> {
195///     let client = Client::new(TOKEN);
196///
197///     let result = client.instances()
198///         .list() // returns this builder
199///         .count(100) // sets the ?count=100 querystring param
200///         // ...etc
201///         .send()?; // actually sends the request
202///     Ok(())
203/// }
204/// ```
205///
206/// # Search instances
207///
208/// ```no_run
209/// use instances_social::Client;
210///
211/// const TOKEN: &'static str = "...";
212///
213/// fn main() -> Result<(), Box<std::error::Error>> {
214///     let client = Client::new(TOKEN);
215///
216///     let result = client.instances()
217///         .search("some-query") // returns this builder
218///         .count(100) // sets the ?count=100 querystring param
219///         // ...etc
220///         .send()?; // actually sends the request
221///     Ok(())
222/// }
223/// ```
224pub struct Instances<'a>(&'a Client);
225impl<'a> Instances<'a> {
226    /// Represents a request to the https://instances.social/api/1.0/instances/sample endpoint
227    ///
228    /// ```no_run
229    /// use instances_social::Client;
230    ///
231    /// const TOKEN: &'static str = "...";
232    ///
233    /// fn main() -> Result<(), Box<std::error::Error>> {
234    ///     let client = Client::new(TOKEN);
235    ///
236    ///     let result = client.instances()
237    ///         .sample() // returns this builder
238    ///         .count(100) // sets the ?count=100 querystring param
239    ///         // ...etc
240    ///         .send()?; // actually sends the request
241    ///     Ok(())
242    /// }
243    /// ```
244    pub fn sample(&self) -> instances::SampleRequestBuilder {
245        instances::SampleRequestBuilder::new(self.0)
246    }
247
248    /// Request builder for the instances/list call
249    ///
250    /// ```no_run
251    /// use instances_social::Client;
252    ///
253    /// const TOKEN: &'static str = "...";
254    ///
255    /// fn main() -> Result<(), Box<std::error::Error>> {
256    ///     let client = Client::new(TOKEN);
257    ///
258    ///     let result = client.instances()
259    ///         .list() // returns this builder
260    ///         .count(100) // sets the ?count=100 querystring param
261    ///         // ...etc
262    ///         .send()?; // actually sends the request
263    ///     Ok(())
264    /// }
265    /// ```
266    pub fn list(&self) -> instances::ListRequestBuilder {
267        instances::ListRequestBuilder::new(self.0)
268    }
269
270    /// Represents a request to the https://instances.social/api/1.0/instances/search endpoint
271    ///
272    /// ```no_run
273    /// use instances_social::Client;
274    ///
275    /// const TOKEN: &'static str = "...";
276    ///
277    /// fn main() -> Result<(), Box<std::error::Error>> {
278    ///     let client = Client::new(TOKEN);
279    ///
280    ///     let result = client.instances()
281    ///         .search("some-query") // returns this builder
282    ///         .count(100) // sets the ?count=100 querystring param
283    ///         // ...etc
284    ///         .send()?; // actually sends the request
285    ///     Ok(())
286    /// }
287    /// ```
288    pub fn search(&self, q: &str) -> instances::SearchRequestBuilder {
289        instances::SearchRequestBuilder::new(self.0, q)
290    }
291
292    /// Represents a request to the https://instances.social/api/1.0/instances/show endpoint
293    ///
294    /// ```no_run
295    /// use instances_social::Client;
296    ///
297    /// const TOKEN: &'static str = "...";
298    ///
299    /// fn main() -> Result<(), Box<std::error::Error>> {
300    ///     let client = Client::new(TOKEN);
301    ///
302    ///     let result = client.instances()
303    ///         .show("mastodon.social") // returns this builder
304    ///         .send()?; // actually sends the request
305    ///     Ok(())
306    /// }
307    /// ```
308    pub fn show(&self, name: &str) -> instances::ShowRequestBuilder {
309        instances::ShowRequestBuilder::new(self.0, name)
310    }
311}
312
313pub(crate) trait Resp {
314    type Item: Clone;
315
316    fn items(&self) -> &Vec<Self::Item>;
317    fn next(&self) -> Option<String>;
318}
319
320pub(crate) trait ItemIter {
321    type Response: Resp;
322
323    fn get_page(&mut self, token: Option<&String>) -> Result<Self::Response, Box<Error>>;
324}
325
326pub(crate) struct ItemIterator<I: ItemIter> {
327    source: I,
328    token: Option<String>,
329    init: bool,
330    buffer: Vec<<I::Response as Resp>::Item>,
331}
332
333impl<I: ItemIter> ItemIterator<I> {
334    pub(crate) fn new(source: I) -> ItemIterator<I> {
335        ItemIterator {
336            source,
337            token: None,
338            init: false,
339            buffer: Vec::new(),
340        }
341    }
342
343    fn fill_buffer(&mut self) -> Result<(), Box<Error>> {
344        if !self.init {
345            let page = self.source.get_page(None)?;
346            self.buffer.extend_from_slice(page.items());
347            self.token = page.next();
348            self.init = true;
349        }
350
351        if let Some(ref t) = self.token {
352            let page = self.source.get_page(Some(t))?;
353            self.buffer.extend_from_slice(page.items());
354            self.token = page.next();
355        }
356
357        Ok(())
358    }
359}
360
361impl<I> Iterator for ItemIterator<I>
362where
363    I: ItemIter,
364{
365    type Item = <<I as ItemIter>::Response as Resp>::Item;
366
367    fn next(&mut self) -> Option<Self::Item> {
368        if self.buffer.is_empty() {
369            if self.fill_buffer().is_err() {
370                return None;
371            }
372        }
373        self.buffer.pop()
374    }
375}