From 7786a83a14e4c93098c3ddec7fc03b07807d325c Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Fri, 8 Mar 2019 06:23:09 -0500 Subject: [PATCH] Add `min_id` to pagination params also switches to using serde_qs instead of manually serializing the query string --- Cargo.toml | 1 + src/errors.rs | 5 ++ src/lib.rs | 3 +- src/requests/statuses.rs | 160 +++++++++++++++++++++++++++++---------- 4 files changed, 126 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6648aaa..23942b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde = "1" serde_derive = "1" serde_json = "1" serde_urlencoded = "0.5.3" +serde_qs = "0.4.5" url = "1" tap-reader = "1" try_from = "0.3.2" diff --git a/src/errors.rs b/src/errors.rs index c98e810..04f7c90 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,6 +5,7 @@ use envy::Error as EnvyError; use hyper_old_types::Error as HeaderParseError; use reqwest::{header::ToStrError as HeaderStrError, Error as HttpError, StatusCode}; use serde_json::Error as SerdeError; +use serde_qs::Error as SerdeQsError; use serde_urlencoded::ser::Error as UrlEncodedError; #[cfg(feature = "toml")] use tomlcrate::de::Error as TomlDeError; @@ -57,6 +58,8 @@ pub enum Error { #[cfg(feature = "env")] /// Error deserializing from the environment Envy(EnvyError), + /// Error serializing to a query string + SerdeQs(SerdeQsError), /// Other errors Other(String), } @@ -96,6 +99,7 @@ impl error::Error for Error { Error::HeaderParseError(ref e) => e.description(), #[cfg(feature = "env")] Error::Envy(ref e) => e.description(), + Error::SerdeQs(ref e) => e.description(), Error::Other(ref e) => e, } } @@ -136,6 +140,7 @@ from! { HeaderStrError, HeaderStrError, HeaderParseError, HeaderParseError, #[cfg(feature = "env")] EnvyError, Envy, + SerdeQsError, SerdeQs, String, Other, } diff --git a/src/lib.rs b/src/lib.rs index 98f3561..098add8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ extern crate serde_json; extern crate chrono; extern crate reqwest; extern crate serde; +extern crate serde_qs; extern crate serde_urlencoded; extern crate tap_reader; extern crate try_from; @@ -389,7 +390,7 @@ impl MastodonClient for Mastodon { let mut url = format!("{}/api/v1/accounts/{}/statuses", self.base, id); if let Some(request) = request.into() { - url = format!("{}{}", url, request.to_querystring()); + url = format!("{}{}", url, request.to_querystring()?); } let response = self.send(self.client.get(&url))?; diff --git a/src/requests/statuses.rs b/src/requests/statuses.rs index 567f809..6e3da08 100644 --- a/src/requests/statuses.rs +++ b/src/requests/statuses.rs @@ -1,5 +1,23 @@ +use errors::Error; +use serde_qs; use std::{borrow::Cow, convert::Into}; +mod bool_qs_serialize { + use serde::Serializer; + + pub fn is_false(b: &bool) -> bool { + !*b + } + + pub fn serialize(b: &bool, s: S) -> Result { + if *b { + s.serialize_i64(1) + } else { + s.serialize_i64(0) + } + } +} + /// Builder for making a client.statuses() call /// /// # Example @@ -9,16 +27,27 @@ use std::{borrow::Cow, convert::Into}; /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); /// request.only_media().pinned().since_id("foo"); -/// # assert_eq!(&request.to_querystring()[..], "?only_media=1&pinned=1&since_id=foo"); +/// # assert_eq!(&request.to_querystring().expect("Couldn't serialize qs")[..], "?only_media=1&pinned=1&since_id=foo"); /// ``` -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Serialize)] pub struct StatusesRequest<'a> { + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] only_media: bool, + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] exclude_replies: bool, + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] pinned: bool, + #[serde(skip_serializing_if = "Option::is_none")] max_id: Option>, + #[serde(skip_serializing_if = "Option::is_none")] since_id: Option>, + #[serde(skip_serializing_if = "Option::is_none")] limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + min_id: Option>, } impl<'a> Into>> for &'a mut StatusesRequest<'a> { @@ -30,6 +59,7 @@ impl<'a> Into>> for &'a mut StatusesRequest<'a> { max_id: self.max_id.clone(), since_id: self.since_id.clone(), limit: self.limit.clone(), + min_id: self.min_id.clone(), }) } } @@ -56,7 +86,7 @@ impl<'a> StatusesRequest<'a> { /// # extern crate elefren; /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); - /// assert_eq!(&request.only_media().to_querystring(), "?only_media=1"); + /// assert_eq!(&request.only_media().to_querystring().expect("Couldn't serialize qs"), "?only_media=1"); pub fn only_media(&mut self) -> &mut Self { self.only_media = true; self @@ -71,7 +101,10 @@ impl<'a> StatusesRequest<'a> { /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); /// assert_eq!( - /// &request.exclude_replies().to_querystring(), + /// &request + /// .exclude_replies() + /// .to_querystring() + /// .expect("Couldn't serialize qs"), /// "?exclude_replies=1" /// ); /// ``` @@ -88,7 +121,13 @@ impl<'a> StatusesRequest<'a> { /// # extern crate elefren; /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); - /// assert_eq!(&request.pinned().to_querystring(), "?pinned=1"); + /// assert_eq!( + /// &request + /// .pinned() + /// .to_querystring() + /// .expect("Couldn't serialize qs"), + /// "?pinned=1" + /// ); /// ``` pub fn pinned(&mut self) -> &mut Self { self.pinned = true; @@ -103,7 +142,13 @@ impl<'a> StatusesRequest<'a> { /// # extern crate elefren; /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); - /// assert_eq!(&request.max_id("foo").to_querystring(), "?max_id=foo"); + /// assert_eq!( + /// &request + /// .max_id("foo") + /// .to_querystring() + /// .expect("Couldn't serialize qs"), + /// "?max_id=foo" + /// ); /// ``` pub fn max_id>>(&mut self, max_id: S) -> &mut Self { self.max_id = Some(max_id.into()); @@ -118,7 +163,13 @@ impl<'a> StatusesRequest<'a> { /// # extern crate elefren; /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); - /// assert_eq!(&request.since_id("foo").to_querystring(), "?since_id=foo"); + /// assert_eq!( + /// &request + /// .since_id("foo") + /// .to_querystring() + /// .expect("Couldn't serialize qs"), + /// "?since_id=foo" + /// ); /// ``` pub fn since_id>>(&mut self, since_id: S) -> &mut Self { self.since_id = Some(since_id.into()); @@ -133,13 +184,40 @@ impl<'a> StatusesRequest<'a> { /// # extern crate elefren; /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); - /// assert_eq!(&request.limit(10).to_querystring(), "?limit=10"); + /// assert_eq!( + /// &request + /// .limit(10) + /// .to_querystring() + /// .expect("Couldn't serialize qs"), + /// "?limit=10" + /// ); /// ``` pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = Some(limit); self } + /// Set the `?min_id=:min_id` flag for the .statuses() request + /// + /// # Example + /// + /// ``` + /// # extern crate elefren; + /// # use elefren::StatusesRequest; + /// let mut request = StatusesRequest::new(); + /// assert_eq!( + /// &request + /// .min_id("foobar") + /// .to_querystring() + /// .expect("Couldn't serialize qs"), + /// "?min_id=foobar" + /// ); + /// ``` + pub fn min_id>>(&mut self, min_id: S) -> &mut Self { + self.min_id = Some(min_id.into()); + self + } + /// Turns this builder into a querystring /// /// # Example @@ -149,42 +227,16 @@ impl<'a> StatusesRequest<'a> { /// # use elefren::StatusesRequest; /// let mut request = StatusesRequest::new(); /// assert_eq!( - /// &request.limit(10).pinned().to_querystring(), + /// &request + /// .limit(10) + /// .pinned() + /// .to_querystring() + /// .expect("Couldn't serialize qs"), /// "?pinned=1&limit=10" /// ); /// ``` - pub fn to_querystring(&self) -> String { - let mut opts = vec![]; - - if self.only_media { - opts.push("only_media=1".into()); - } - - if self.exclude_replies { - opts.push("exclude_replies=1".into()); - } - - if self.pinned { - opts.push("pinned=1".into()); - } - - if let Some(ref max_id) = self.max_id { - opts.push(format!("max_id={}", max_id)); - } - - if let Some(ref since_id) = self.since_id { - opts.push(format!("since_id={}", since_id)); - } - - if let Some(limit) = self.limit { - opts.push(format!("limit={}", limit)); - } - - if opts.is_empty() { - String::new() - } else { - format!("?{}", opts.join("&")) - } + pub fn to_querystring(&self) -> Result { + Ok(format!("?{}", serde_qs::to_string(&self)?)) } } @@ -204,6 +256,7 @@ mod tests { max_id: None, since_id: None, limit: None, + min_id: None, } ); } @@ -221,6 +274,7 @@ mod tests { max_id: None, since_id: None, limit: None, + min_id: None, } ); } @@ -238,6 +292,7 @@ mod tests { max_id: None, since_id: None, limit: None, + min_id: None, } ); } @@ -254,6 +309,7 @@ mod tests { max_id: None, since_id: None, limit: None, + min_id: None, } ); } @@ -270,6 +326,7 @@ mod tests { max_id: Some("foo".into()), since_id: None, limit: None, + min_id: None, } ); } @@ -286,6 +343,7 @@ mod tests { max_id: None, since_id: Some("foo".into()), limit: None, + min_id: None, } ); } @@ -302,6 +360,24 @@ mod tests { max_id: None, since_id: None, limit: Some(42), + min_id: None, + } + ); + } + #[test] + fn test_min_id() { + let mut request = StatusesRequest::new(); + request.min_id("foo"); + assert_eq!( + request, + StatusesRequest { + only_media: false, + exclude_replies: false, + pinned: false, + max_id: None, + since_id: None, + limit: None, + min_id: Some("foo".into()), } ); } @@ -312,7 +388,7 @@ mod tests { { let mut $r = StatusesRequest::new(); $b - let qs = $r.to_querystring(); + let qs = $r.to_querystring().expect("Failed to serialize querystring"); assert_eq!(&qs, $expected); } }