From b6d350f29e026bdb92a6ad3d3ef6e9fd449470e4 Mon Sep 17 00:00:00 2001 From: Aaron Power Date: Thu, 9 Nov 2017 13:05:30 +0000 Subject: [PATCH] updated reqwest and fixed media route reqwest updated to 0.8 removed Result for creating Mastodon client as result of reqwest update added Meta and ImageDetails struct for Attachment Changed Attachment.id to String Added test for media route. Changed paramaters for media from Vec of image data to String of file path. Added dotenv for testing --- .env.sample | 5 +++ .gitignore | 1 + src/entities/attachment.rs | 21 +++++++++- src/lib.rs | 84 +++++++++++++++++++++++++++++++------ src/registration.rs | 10 ++--- tests/test.png | Bin 0 -> 3118 bytes tests/upload_photo.rs | 29 +++++++++++++ 7 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 .env.sample create mode 100644 tests/test.png create mode 100644 tests/upload_photo.rs diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..693aa30 --- /dev/null +++ b/.env.sample @@ -0,0 +1,5 @@ +export TOKEN='snakeoil' +export CLIENT_ID='' +export CLIENT_SECRET='' +export REDIRECT='' +export BASE='https://mastodon.social' diff --git a/.gitignore b/.gitignore index a9d37c5..e08f5fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.env diff --git a/src/entities/attachment.rs b/src/entities/attachment.rs index 5a0376f..5d67e13 100644 --- a/src/entities/attachment.rs +++ b/src/entities/attachment.rs @@ -1,12 +1,29 @@ #[derive(Debug, Clone, Deserialize)] pub struct Attachment { - pub id: u64, + pub id: String, #[serde(rename="type")] pub media_type: MediaType, pub url: String, - pub remote_url: String, + pub remote_url: Option, pub preview_url: String, pub text_url: Option, + pub meta: Option, + pub description: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Meta { + original: ImageDetails, + small: ImageDetails, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct ImageDetails { + width: u64, + height: u64, + size: String, + aspect: f64, + } #[derive(Debug, Deserialize, Clone, Copy)] diff --git a/src/lib.rs b/src/lib.rs index 04dad61..fad49b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ //! website: None, //! }; //! -//! let mut registration = Registration::new("https://mastodon.social")?; +//! let mut registration = Registration::new("https://mastodon.social"); //! registration.register(app)?; //! let url = registration.authorise()?; //! // Here you now need to open the url in the browser @@ -56,7 +56,7 @@ use std::io::Error as IoError; use json::Error as SerdeError; use reqwest::Error as HttpError; -use reqwest::Client; +use reqwest::{Client, StatusCode}; use reqwest::header::{Authorization, Bearer, Headers}; use entities::prelude::*; @@ -92,6 +92,49 @@ macro_rules! methods { macro_rules! route { + ((post multipart ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { + /// Equivalent to `/api/v1/ + #[doc = $url] + /// ` + /// + #[doc = "# Errors"] + /// If `access_token` is not set. + pub fn $name(&self, $($param: $typ,)*) -> Result<$ret> { + use std::io::Read; + use reqwest::multipart::Form; + + let form_data = Form::new() + $( + .file(stringify!($param), $param)? + )*; + + let mut response = self.client.post(&self.route(concat!("/api/v1/", $url))) + .headers(self.headers.clone()) + .multipart(form_data) + .send()?; + + let status = response.status().clone(); + + if status.is_client_error() { + return Err(Error::Client(status)); + } else if status.is_server_error() { + return Err(Error::Server(status)); + } + + let mut vec = Vec::new(); + + response.read_to_end(&mut vec)?; + + + match json::from_slice::<$ret>(&vec) { + Ok(res) => Ok(res), + Err(_) => Err(Error::Api(json::from_slice(&vec)?)), + } + } + + route!{$($rest)*} + }; + ((post ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => { /// Equivalent to `/api/v1/ #[doc = $url] @@ -105,24 +148,32 @@ macro_rules! route { let form_data = json!({ $( stringify!($param): $param, - )* + )* }); let mut response = self.client.post(&self.route(concat!("/api/v1/", $url))) .headers(self.headers.clone()) - .form(&form_data) + .json(&form_data) .send()?; + let status = response.status().clone(); + + if status.is_client_error() { + return Err(Error::Client(status)); + } else if status.is_server_error() { + return Err(Error::Server(status)); + } + let mut vec = Vec::new(); response.read_to_end(&mut vec)?; - if let Ok(t) = json::from_slice(&vec) { - Ok(t) - } else { - Err(Error::Api(json::from_slice(&vec)?)) + match json::from_slice(&vec) { + Ok(res) => Ok(res), + Err(_) => Err(Error::Api(json::from_slice(&vec)?)), } } + route!{$($rest)*} }; @@ -196,6 +247,10 @@ pub enum Error { ClientSecretRequired, #[serde(skip_deserializing)] AccessTokenRequired, + #[serde(skip_deserializing)] + Client(StatusCode), + #[serde(skip_deserializing)] + Server(StatusCode), } impl fmt::Display for Error { @@ -211,6 +266,9 @@ impl StdError for Error { Error::Serde(ref e) => e.description(), Error::Http(ref e) => e.description(), Error::Io(ref e) => e.description(), + Error::Client(ref status) | Error::Server(ref status) => { + status.canonical_reason().unwrap_or("Unknown Status code") + }, Error::ClientIdRequired => "ClientIdRequired", Error::ClientSecretRequired => "ClientSecretRequired", Error::AccessTokenRequired => "AccessTokenRequired", @@ -256,15 +314,15 @@ impl Mastodon { } /// Creates a mastodon instance from the data struct. - pub fn from_data(data: Data) -> Result { + pub fn from_data(data: Data) -> Self { let mut headers = Headers::new(); headers.set(Authorization(Bearer { token: data.token.clone() })); - Ok(Mastodon { - client: Client::new()?, + Mastodon { + client: Client::new(), headers: headers, data: data, - }) + } } route! { @@ -279,7 +337,7 @@ impl Mastodon { (post (id: u64,)) reject_follow_request: "accounts/follow_requests/reject" => Empty, (post (uri: String,)) follows: "follows" => Account, (post) clear_notifications: "notifications/clear" => Empty, - (post (file: Vec,)) media: "media" => Attachment, + (post multipart (file: String,)) media: "media" => Attachment, (post (account_id: u64, status_ids: Vec, comment: String,)) report: "reports" => Report, (post (q: String, resolve: bool,)) search: "search" => SearchResult, diff --git a/src/registration.rs b/src/registration.rs index d725640..0407e1a 100644 --- a/src/registration.rs +++ b/src/registration.rs @@ -27,15 +27,15 @@ struct AccessToken { } impl Registration { - pub fn new>(base: I) -> Result { - Ok(Registration { + pub fn new>(base: I) -> Self { + Registration { base: base.into(), - client: Client::new()?, + client: Client::new(), client_id: None, client_secret: None, redirect: None, scopes: Scope::Read, - }) + } } /// Register the application with the server from the `base` url. @@ -56,7 +56,7 @@ impl Registration { /// website: None, /// }; /// - /// let mut registration = Registration::new("https://mastodon.social")?; + /// let mut registration = Registration::new("https://mastodon.social"); /// registration.register(app)?; /// let url = registration.authorise()?; /// // Here you now need to open the url in the browser diff --git a/tests/test.png b/tests/test.png new file mode 100644 index 0000000000000000000000000000000000000000..89bf64971af7ce3250fe59743687bd7930cb24a6 GIT binary patch literal 3118 zcmb7GeK^x=AD@Gmx13~%LoY^Ys8D&yj=UvLCJ{?mnnZ*l#K)oo)~7MIh)QO&vRYR^*lY-@4D~r=l*`b_vicl-PiBFexC=fE{=+F zyW{`>fTGhWJ2wD80wNx=+eDr?bZWH7NJP3ho&;3)X-$h3+x(p!?EstN=Sfp(y2t|z zKLw8j0OVD~Ljr)$RTnvBqMXjy%M8h=%WsoIKL6w^a%n`_dqvrXg@y)Pj{?}D0xm@b zAa}n74e04bRIR@Xy+*CLQa`xU66xnTU~LL;USr;K7pjE z`#8C`u1^eh9w^N6wg(TOXP+@9kJR-b)1?QYI{mPW-oof-{DOxctQMni^J0*E096 zp!%T(&&@hC_49RA2z5tg@?37G_mMCmWug#f?JHOan1>nljK*xh^OJ615Wq}? zqOOkMZ4H0cvN>}x-B&%F&HQj?e2KyL>uN96lg~r|&)37;b7~Na?0~U2ZJneC5`6X61<%JfEmeKnjILf`*kWFv4+FqSwcs@*&bWDWu#1uPKB_mo;Eix`T|-yOcMLW)|+y=WDhXCn(jU-~c-0Dmz7x_umo%^XRCM`mfbYUnJUxty*K&p? zD8aV!W@pbe?Jh<6=1a;Ld)TYhr|TQ!lB`~QBjh71iXbYhe_1f6T7N^hRX*Q(dQ1A!hf2 z!;wKeYh?(_CSaj<`qi)&(W!$m8gD~dgBnXO9F>ay5kmBvOgfKRUgx;tj0Y&}CLJlP zt~s7;KB`U}${A?n-9a$O`?h+z!pIdORn#PoF04IdIdqi*aO{J zha}@wAzi3Tlo^>9s|H>fb{}t;kUYh_Z@hb7`$}VOJd+#>&D$I#M_uZy} zE%2c0`nO!Q)x>Wg0Yz$(1C`JbQd?XRM2*1;#Mw;w#=m#gIpC8;2?0YNWO}y9L#~mx zQg+;xXNrpThg^I3ZA;*oiJYyt5nZ)a*DZ3pU8On6XgLs={>90J1QWHWX~|aDdck%F zfkc%!bOAm-`Rk?KS2xHM48}uVO%T}oT3Z^MRz?^LxN|~O`0)OqkYg!sgzDi&-hBka zp+>4(C_4U-&)a|5S;cX6u)oF=Dp%r zDDX!WcWV)uPl_WNL^*W0lQ5ym+`V?s_6~h3c~FN{lRkVU&D^~t1?n=hNt-@RT4RHR zEegc`KkjlBY)G*=Y;Bk82_ss>(_D(4{p^Wtd%VcEI}-CM2d^l36hvg^*hyo@`NJs_ z;icxw&z>T`ocw(UBz4j?aA^p~p*>5n*5+LVf)@HyB|D>MdkXv%;`Wzo&PIW~$ZD|W zv=L#3`EofmMsG#!0S*zdx0!bm^{$3P)5kYwmm_|m@)Y?vy zrhuDkQ7FpQ6}L)`-kGF>=S6qDT(_P(J>i|xQy6%8R$RmYGKD{?P31C(%u3` zQ?yx7gMGOambEp26(zpzRQn$g3G4p3)R$Awen1PVQbWmilur0;?5cT?~VRma#viMsGQaQ5N= za>0fhi!C(N5Y!-PW@=Vj&K(u%W!FDnXI0fUZtHTU1da0C6@5I5g$V@) ztz8dq@%*{XNa4G*57f?2Md_5sZY^PjK{oxdjB^Ku`(DyfVwv;zPdE6h_zN6*8b5pA zS9hG9m2flUNTk0ilx7o|-d>U35GijAk#Z)T@_F>o99;jUa)Jo#gxd mammut::Result<()> { + + let data = Data { + base: String::from(env::var("BASE").unwrap()), + client_id: String::from(env::var("CLIENT_ID").unwrap()), + client_secret: String::from(env::var("CLIENT_SECRET").unwrap()), + redirect: String::from(env::var("REDIRECT").unwrap()), + token: String::from(env::var("TOKEN").unwrap()), + }; + + let mastodon = Mastodon::from_data(data); + + mastodon.media("tests/test.png".into())?; + Ok(()) +}