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}