rive_autumn/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use futures::TryStreamExt;
4use reqwest::{
5    multipart::{Form, Part},
6    Body,
7};
8use rive_models::{
9    autumn::{Config, UploadData},
10    error::AutumnError,
11};
12use tokio::io::AsyncRead;
13use tokio_util::{
14    codec::{BytesCodec, FramedRead},
15    io::StreamReader,
16};
17
18/// Revolt official instance base URL
19pub const BASE_URL: &str = "https://autumn.revolt.chat";
20
21type Result<T> = std::result::Result<T, Error>;
22
23/// Client error
24#[derive(Debug, thiserror::Error)]
25pub enum Error {
26    /// Data serialization/deserialization error
27    #[error("Serde JSON serialization/deserialization error: {0}")]
28    Serialization(#[from] serde_json::Error),
29
30    /// HTTP error
31    #[error("Error while processing an HTTP request: {0}")]
32    HttpRequest(#[from] reqwest::Error),
33
34    /// An error returned from Autumn API
35    #[error("Error returned from API")]
36    Api(AutumnError),
37}
38
39/// A wrapper for Autumn API
40#[derive(Debug, Clone)]
41pub struct Client {
42    base_url: String,
43    client: reqwest::Client,
44}
45
46impl Default for Client {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl Client {
53    /// Create a client with Revolt official instance base URL.
54    pub fn new() -> Self {
55        Client::new_base_url(BASE_URL)
56    }
57
58    /// Create a client instance with given base URL.
59    pub fn new_base_url(base_url: impl Into<String>) -> Self {
60        Client {
61            base_url: base_url.into(),
62            client: reqwest::Client::new(),
63        }
64    }
65
66    /// Fetch the configuration of Autumn instance.
67    pub async fn fetch_config(&self) -> Result<Config> {
68        let response = self
69            .client
70            .get(format!("{}/", self.base_url))
71            .send()
72            .await?;
73
74        match response.status().as_u16() {
75            200..=299 => Ok(response.json().await?),
76            _ => Err(Error::Api(response.json().await?)),
77        }
78    }
79
80    /// Download an attachment by its tag and ID.
81    pub async fn download(
82        &self,
83        tag: impl Into<String>,
84        id: impl Into<String>,
85    ) -> Result<impl AsyncRead> {
86        let response = self
87            .client
88            .get(format!("{}/{}/{}", self.base_url, tag.into(), id.into()))
89            .send()
90            .await?;
91
92        match response.status().as_u16() {
93            200..=299 => {
94                let st = StreamReader::new(
95                    response
96                        .bytes_stream()
97                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)),
98                );
99                Ok(st)
100            }
101            _ => Err(Error::Api(response.json().await?)),
102        }
103    }
104
105    /// Upload an attachment.
106    pub async fn upload(
107        &self,
108        tag: impl Into<String>,
109        filename: impl Into<String>,
110        contents: impl AsyncRead + Send + Sync + 'static,
111    ) -> Result<UploadData> {
112        let stream = FramedRead::new(contents, BytesCodec::new());
113        let body = Body::wrap_stream(stream);
114        let part = Part::stream(body).file_name(filename.into());
115        let form = Form::new().part("file", part);
116
117        let response = self
118            .client
119            .post(format!("{}/{}", self.base_url, tag.into()))
120            .multipart(form)
121            .send()
122            .await?;
123
124        match response.status().as_u16() {
125            200..=299 => Ok(response.json().await?),
126            _ => Err(Error::Api(response.json().await?)),
127        }
128    }
129}