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
18pub const BASE_URL: &str = "https://autumn.revolt.chat";
20
21type Result<T> = std::result::Result<T, Error>;
22
23#[derive(Debug, thiserror::Error)]
25pub enum Error {
26 #[error("Serde JSON serialization/deserialization error: {0}")]
28 Serialization(#[from] serde_json::Error),
29
30 #[error("Error while processing an HTTP request: {0}")]
32 HttpRequest(#[from] reqwest::Error),
33
34 #[error("Error returned from API")]
36 Api(AutumnError),
37}
38
39#[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 pub fn new() -> Self {
55 Client::new_base_url(BASE_URL)
56 }
57
58 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 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 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 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}