diff options
author | D. Scott Boggs <scott@tams.tech> | 2023-09-29 17:18:40 -0400 |
---|---|---|
committer | D. Scott Boggs <scott@tams.tech> | 2023-09-29 17:18:40 -0400 |
commit | 6aca4c12a54aeb6f130c1b766cf2ab8b33d774fd (patch) | |
tree | 36e142eee4a64ee3c5bc035169acabf9356f9e30 | |
parent | 15168b5e318a5a246b13dbfd8d07109b183a8a81 (diff) |
Refactor statuses request filters options
-rw-r--r-- | .vscode/settings.json | 1 | ||||
-rw-r--r-- | entities/src/error.rs | 3 | ||||
-rw-r--r-- | entities/src/forms/mod.rs | 1 | ||||
-rw-r--r-- | entities/src/forms/status_request.rs | 73 | ||||
-rw-r--r-- | examples/get_statuses.rs | 7 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/mastodon.rs | 18 | ||||
-rw-r--r-- | src/page.rs | 4 | ||||
-rw-r--r-- | src/requests/mod.rs | 3 | ||||
-rw-r--r-- | src/requests/statuses.rs | 583 |
10 files changed, 94 insertions, 603 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 16ca026..486186f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "isolang", "querystring", "reblog", + "reblogs", "repr", "reqwest", "subscope", diff --git a/entities/src/error.rs b/entities/src/error.rs index 427dcaa..30a7d70 100644 --- a/entities/src/error.rs +++ b/entities/src/error.rs @@ -11,6 +11,9 @@ pub enum Error { Builder(#[from] derive_builder::UninitializedFieldError), #[error(transparent)] UrlEncodingError(serde_urlencoded::ser::Error), + /// Error serializing to url-encoded string + #[error("error serializing to url-encoded string")] + UrlEncoded(#[from] serde_urlencoded::ser::Error), } pub type Result<T> = std::result::Result<T, Error>; diff --git a/entities/src/forms/mod.rs b/entities/src/forms/mod.rs index 06a7aa1..19725f4 100644 --- a/entities/src/forms/mod.rs +++ b/entities/src/forms/mod.rs @@ -1,5 +1,6 @@ pub mod account; pub mod application; pub mod oauth; +pub mod status_request; pub use application::{Application, ApplicationBuilder}; diff --git a/entities/src/forms/status_request.rs b/entities/src/forms/status_request.rs new file mode 100644 index 0000000..0c11a14 --- /dev/null +++ b/entities/src/forms/status_request.rs @@ -0,0 +1,73 @@ +use crate::error::Result; +use derive_builder::Builder; +use serde::Serialize; + +mod bool_qs_serialize { + use serde::Serializer; + + pub fn is_false(b: &bool) -> bool { + !*b + } + + pub fn serialize<S: Serializer>(b: &bool, s: S) -> Result<S::Ok, S::Error> { + if *b { + s.serialize_i64(1) + } else { + s.serialize_i64(0) + } + } +} + +#[derive(Debug, Default, Serialize, Builder)] +#[builder( + derive(Debug, PartialEq), + build_fn(error = "crate::Error", private, name = "try_build") +)] +pub struct Options { + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] + #[builder(default)] + only_media: bool, + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] + #[builder(default)] + exclude_replies: bool, + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] + #[builder(default)] + pinned: bool, + #[serde(skip_serializing_if = "Option::is_none")] + #[builder(default, setter(into, strip_option))] + max_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + #[builder(default, setter(into, strip_option))] + since_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + limit: Option<usize>, + #[serde(skip_serializing_if = "Option::is_none")] + #[builder(default, setter(into, strip_option))] + min_id: Option<String>, + #[serde(skip_serializing_if = "bool_qs_serialize::is_false")] + #[serde(serialize_with = "bool_qs_serialize::serialize")] + #[builder(default)] + exclude_reblogs: bool, + #[serde(skip_serializing_if = "Option::is_none")] + #[builder(default, setter(into, strip_option))] + tagged: Option<String>, +} + +impl Options { + pub fn to_query_string(&self) -> Result<String> { + Ok(format!("&{}", serde_urlencoded::to_string(self)?)) + } + pub fn builder() -> OptionsBuilder { + OptionsBuilder::default() + } +} + +impl OptionsBuilder { + pub fn build(&self) -> Options { + self.try_build().expect("no options are required") + } +} diff --git a/examples/get_statuses.rs b/examples/get_statuses.rs index bab117a..90b1c89 100644 --- a/examples/get_statuses.rs +++ b/examples/get_statuses.rs @@ -1,20 +1,19 @@ #![cfg_attr(not(feature = "toml"), allow(dead_code))] #![cfg_attr(not(feature = "toml"), allow(unused_imports))] mod register; -use mastodon_async::Result; +use mastodon_async::{errors::Result, prelude::*}; #[cfg(feature = "toml")] async fn run() -> Result<()> { use futures_util::StreamExt; - use mastodon_async::StatusesRequest; - let mut filters = StatusesRequest::new(); + let mut filters = forms::status_request::Options::builder(); filters.limit(3); let mastodon = register::get_mastodon_data().await?; let you = mastodon.verify_credentials().await?; mastodon - .statuses(&you.id, filters) + .statuses(&you.id, filters.build()) .await? .items_iter() .take(4) @@ -106,7 +106,7 @@ pub use mastodon_async_entities::{ status::NewStatus, status::NewStatusBuilder, visibility::Visibility, }; pub use registration::Registration; -pub use requests::{AddFilterRequest, AddPushRequest, StatusesRequest, UpdatePushRequest}; +pub use requests::{AddFilterRequest, AddPushRequest, UpdatePushRequest}; /// Contains the struct that holds the client auth data pub mod data; @@ -133,7 +133,7 @@ pub mod polling_time; pub mod prelude { pub use crate::{ entities::prelude::*, Data, Mastodon, MastodonUnauthenticated, NewStatus, NewStatusBuilder, - Registration, StatusesRequest, Visibility, + Registration, Visibility, }; // Legacy alias; TODO remove for 2.0 pub use super::entities::status::NewStatusBuilder as StatusBuilder; diff --git a/src/mastodon.rs b/src/mastodon.rs index 5a93713..feb242c 100644 --- a/src/mastodon.rs +++ b/src/mastodon.rs @@ -9,7 +9,7 @@ use crate::{ errors::{Error, Result}, helpers::read_response::read_response, polling_time::PollingTime, - AddFilterRequest, AddPushRequest, Data, NewStatus, Page, StatusesRequest, UpdatePushRequest, + AddFilterRequest, AddPushRequest, Data, NewStatus, Page, UpdatePushRequest, }; use futures::TryStream; use reqwest::{multipart::Part, Client, RequestBuilder}; @@ -249,20 +249,20 @@ impl Mastodon { /// tokio_test::block_on(async { /// let data = Data::default(); /// let client = Mastodon::from(data); - /// let mut request = StatusesRequest::new(); - /// request.only_media(); - /// let statuses = client.statuses(&AccountId::new("user-id"), request).await.unwrap(); + /// let mut options = forms::status_request::Options::builder(); + /// options.only_media(true); + /// let statuses = client.statuses(&AccountId::new("user-id"), options.build()).await.unwrap(); /// }); /// ``` - pub async fn statuses<'a, 'b: 'a>( - &'b self, - id: &'b AccountId, - request: StatusesRequest<'a>, + pub async fn statuses( + &self, + id: &AccountId, + options: forms::status_request::Options, ) -> Result<Page<Status>> { let call_id = Uuid::new_v4(); let mut url = format!("{}/api/v1/accounts/{id}/statuses", self.data.base); - url += request.to_query_string()?.as_str(); + url += options.to_query_string()?.as_str(); debug!( url, diff --git a/src/page.rs b/src/page.rs index be02b5a..c4ad21a 100644 --- a/src/page.rs +++ b/src/page.rs @@ -166,13 +166,13 @@ impl<T: Clone + for<'de> Deserialize<'de> + Serialize + Debug> Page<T> { /// ```no_run /// use mastodon_async::prelude::*; /// use futures_util::StreamExt; + /// use std::default::Default; /// /// let data = Data::default(); /// let mastodon = Mastodon::from(data); - /// let req = StatusesRequest::new(); /// /// tokio_test::block_on(async { - /// let resp = mastodon.statuses(&AccountId::new("some-id"), req).await.unwrap(); + /// let resp = mastodon.statuses(&AccountId::new("some-id"), Default::default()).await.unwrap(); /// resp.items_iter().for_each(|status| async move { /// // do something with status /// }).await; diff --git a/src/requests/mod.rs b/src/requests/mod.rs index a855c5d..b792bbe 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -2,9 +2,6 @@ pub use self::filter::AddFilterRequest; /// Data structure for the MastodonClient::add_push_subscription method pub use self::push::{AddPushRequest, Keys, UpdatePushRequest}; -/// Data structure for the MastodonClient::statuses method -pub use self::statuses::StatusesRequest; mod filter; mod push; -mod statuses; diff --git a/src/requests/statuses.rs b/src/requests/statuses.rs deleted file mode 100644 index f44ff5f..0000000 --- a/src/requests/statuses.rs +++ /dev/null @@ -1,583 +0,0 @@ -use crate::errors::Error; -use serde::Serialize; -use std::{borrow::Cow, convert::Into}; - -mod bool_qs_serialize { - use serde::Serializer; - - pub fn is_false(b: &bool) -> bool { - !*b - } - - pub fn serialize<S: Serializer>(b: &bool, s: S) -> Result<S::Ok, S::Error> { - if *b { - s.serialize_i64(1) - } else { - s.serialize_i64(0) - } - } -} - -/// Builder for making a client.statuses() call -/// -/// // Example -/// -/// ``` -/// use mastodon_async::requests::StatusesRequest; -/// let mut request = StatusesRequest::new(); -/// request.only_media().pinned().since_id("foo"); -/// assert_eq!(&request.to_query_string().expect("Couldn't serialize qs")[..], "?only_media=1&pinned=1&since_id=foo"); -/// ``` -#[derive(Clone, Debug, Default, PartialEq, Eq, 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<Cow<'a, str>>, - #[serde(skip_serializing_if = "Option::is_none")] - since_id: Option<Cow<'a, str>>, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option<usize>, - #[serde(skip_serializing_if = "Option::is_none")] - min_id: Option<Cow<'a, str>>, -} - -impl<'a> From<&'a mut StatusesRequest<'a>> for Option<StatusesRequest<'a>> { - fn from(sr: &'a mut StatusesRequest<'a>) -> Self { - Some(StatusesRequest { - only_media: sr.only_media, - exclude_replies: sr.exclude_replies, - pinned: sr.pinned, - max_id: sr.max_id.clone(), - since_id: sr.since_id.clone(), - limit: sr.limit, - min_id: sr.min_id.clone(), - }) - } -} - -impl<'a> StatusesRequest<'a> { - /// Construct a new `StatusesRequest` object - pub fn new() -> Self { - Self::default() - } - - /// Set the `?only_media=1` flag for the .statuses() request - /// - /// // Example - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!(&request.only_media().to_query_string().expect("Couldn't serialize qs"), "?only_media=1"); - /// ``` - pub fn only_media(&mut self) -> &mut Self { - self.only_media = true; - self - } - - /// Set the `?exclude_replies=1` flag for the .statuses() request - /// - /// // Example - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!( - /// &request - /// .exclude_replies() - /// .to_query_string() - /// .expect("Couldn't serialize qs"), - /// "?exclude_replies=1" - /// ); - /// ``` - pub fn exclude_replies(&mut self) -> &mut Self { - self.exclude_replies = true; - self - } - - /// Set the `?pinned=1` flag for the .statuses() request - /// - /// // Example - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!( - /// &request - /// .pinned() - /// .to_query_string() - /// .expect("Couldn't serialize qs"), - /// "?pinned=1" - /// ); - /// ``` - pub fn pinned(&mut self) -> &mut Self { - self.pinned = true; - self - } - - /// Set the `?max_id=:max_id` flag for the .statuses() request - /// - /// // Example - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!( - /// &request - /// .max_id("foo") - /// .to_query_string() - /// .expect("Couldn't serialize qs"), - /// "?max_id=foo" - /// ); - /// ``` - pub fn max_id<S: Into<Cow<'a, str>>>(&mut self, max_id: S) -> &mut Self { - self.max_id = Some(max_id.into()); - self - } - - /// Set the `?since_id=:since_id` flag for the .statuses() request - /// - /// // Example - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!( - /// &request - /// .since_id("foo") - /// .to_query_string() - /// .expect("Couldn't serialize qs"), - /// "?since_id=foo" - /// ); - /// ``` - pub fn since_id<S: Into<Cow<'a, str>>>(&mut self, since_id: S) -> &mut Self { - self.since_id = Some(since_id.into()); - self - } - - /// Set the `?limit=:limit` flag for the .statuses() request - /// - /// // Example - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!( - /// &request - /// .limit(10) - /// .to_query_string() - /// .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 - /// - /// ``` - /// use mastodon_async::requests::StatusesRequest; - /// let mut request = StatusesRequest::new(); - /// assert_eq!( - /// &request - /// .min_id("foobar") - /// .to_query_string() - /// .expect("Couldn't serialize qs"), - /// "?min_id=foobar" - /// ); - /// ``` - pub fn min_id<S: Into<Cow<'a, str>>>(&mut self, min_id: S) -> &mut Self { - self.min_id = Some(min_id.into()); - self - } - - /// Serialize into a query string - pub fn to_query_string(&self) -> Result<String, Error> { - Ok(format!("?{}", serde_urlencoded::to_string(self)?)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new() { - let request = StatusesRequest::new(); - assert_eq!( - request, - StatusesRequest { - only_media: false, - exclude_replies: false, - pinned: false, - max_id: None, - since_id: None, - limit: None, - min_id: None, - } - ); - } - - #[test] - fn test_only_media() { - let mut request = StatusesRequest::new(); - request.only_media(); - assert_eq!( - request, - StatusesRequest { - only_media: true, - exclude_replies: false, - pinned: false, - max_id: None, - since_id: None, - limit: None, - min_id: None, - } - ); - } - - #[test] - fn test_exclude_replies() { - let mut request = StatusesRequest::new(); - request.exclude_replies(); - assert_eq!( - request, - StatusesRequest { - only_media: false, - exclude_replies: true, - pinned: false, - max_id: None, - since_id: None, - limit: None, - min_id: None, - } - ); - } - #[test] - fn test_pinned() { - let mut request = StatusesRequest::new(); - request.pinned(); - assert_eq!( - request, - StatusesRequest { - only_media: false, - exclude_replies: false, - pinned: true, - max_id: None, - since_id: None, - limit: None, - min_id: None, - } - ); - } - #[test] - fn test_max_id() { - let mut request = StatusesRequest::new(); - request.max_id("foo"); - assert_eq!( - request, - StatusesRequest { - only_media: false, - exclude_replies: false, - pinned: false, - max_id: Some("foo".into()), - since_id: None, - limit: None, - min_id: None, - } - ); - } - #[test] - fn test_since_id() { - let mut request = StatusesRequest::new(); - request.since_id("foo"); - assert_eq!( - request, - StatusesRequest { - only_media: false, - exclude_replies: false, - pinned: false, - max_id: None, - since_id: Some("foo".into()), - limit: None, - min_id: None, - } - ); - } - #[test] - fn test_limit() { - let mut request = StatusesRequest::new(); - request.limit(42); - assert_eq!( - request, - StatusesRequest { - only_media: false, - exclude_replies: false, - pinned: false, - 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()), - } - ); - } - #[test] - fn test_to_query_string() { - macro_rules! qs_test { - (|$r:ident| $b:block, $expected:expr) => { - { - let mut $r = StatusesRequest::new(); - $b - let qs = $r.to_query_string().expect("Failed to serialize querystring"); - assert_eq!(&qs, $expected); - } - } - } - - qs_test!( - |request| { - request.only_media(); - }, - "?only_media=1" - ); - qs_test!( - |request| { - request.exclude_replies(); - }, - "?exclude_replies=1" - ); - qs_test!( - |request| { - request.pinned(); - }, - "?pinned=1" - ); - qs_test!( - |request| { - request.max_id("foo"); - }, - "?max_id=foo" - ); - qs_test!( - |request| { - request.since_id("foo"); - }, - "?since_id=foo" - ); - qs_test!( - |request| { - request.limit(42); - }, - "?limit=42" - ); - qs_test!( - |request| { - request.only_media().exclude_replies(); - }, - "?only_media=1&exclude_replies=1" - ); - qs_test!( - |request| { - request.only_media().pinned(); - }, - "?only_media=1&pinned=1" - ); - qs_test!( - |request| { - request.only_media().max_id("foo"); - }, - "?only_media=1&max_id=foo" - ); - qs_test!( - |request| { - request.only_media().since_id("foo"); - }, - "?only_media=1&since_id=foo" - ); - qs_test!( - |request| { - request.only_media().limit(42); - }, - "?only_media=1&limit=42" - ); - qs_test!( - |request| { - request.exclude_replies().only_media(); - }, - "?only_media=1&exclude_replies=1" - ); - qs_test!( - |request| { - request.exclude_replies().pinned(); - }, - "?exclude_replies=1&pinned=1" - ); - qs_test!( - |request| { - request.exclude_replies().max_id("foo"); - }, - "?exclude_replies=1&max_id=foo" - ); - qs_test!( - |request| { - request.exclude_replies().since_id("foo"); - }, - "?exclude_replies=1&since_id=foo" - ); - qs_test!( - |request| { - request.exclude_replies().limit(42); - }, - "?exclude_replies=1&limit=42" - ); - qs_test!( - |request| { - request.pinned().only_media(); - }, - "?only_media=1&pinned=1" - ); - qs_test!( - |request| { - request.pinned().exclude_replies(); - }, - "?exclude_replies=1&pinned=1" - ); - qs_test!( - |request| { - request.pinned().max_id("foo"); - }, - "?pinned=1&max_id=foo" - ); - qs_test!( - |request| { - request.pinned().since_id("foo"); - }, - "?pinned=1&since_id=foo" - ); - qs_test!( - |request| { - request.pinned().limit(42); - }, - "?pinned=1&limit=42" - ); - qs_test!( - |request| { - request.max_id("foo").only_media(); - }, - "?only_media=1&max_id=foo" - ); - qs_test!( - |request| { - request.max_id("foo").exclude_replies(); - }, - "?exclude_replies=1&max_id=foo" - ); - qs_test!( - |request| { - request.max_id("foo").pinned(); - }, - "?pinned=1&max_id=foo" - ); - qs_test!( - |request| { - request.max_id("foo").since_id("foo"); - }, - "?max_id=foo&since_id=foo" - ); - qs_test!( - |request| { - request.max_id("foo").limit(42); - }, - "?max_id=foo&limit=42" - ); - qs_test!( - |request| { - request.since_id("foo").only_media(); - }, - "?only_media=1&since_id=foo" - ); - qs_test!( - |request| { - request.since_id("foo").exclude_replies(); - }, - "?exclude_replies=1&since_id=foo" - ); - qs_test!( - |request| { - request.since_id("foo").pinned(); - }, - "?pinned=1&since_id=foo" - ); - qs_test!( - |request| { - request.since_id("foo").max_id("foo"); - }, - "?max_id=foo&since_id=foo" - ); - qs_test!( - |request| { - request.since_id("foo").limit(42); - }, - "?since_id=foo&limit=42" - ); - qs_test!( - |request| { - request.limit(42).only_media(); - }, - "?only_media=1&limit=42" - ); - qs_test!( - |request| { - request.limit(42).exclude_replies(); - }, - "?exclude_replies=1&limit=42" - ); - qs_test!( - |request| { - request.limit(42).pinned(); - }, - "?pinned=1&limit=42" - ); - qs_test!( - |request| { - request.limit(42).max_id("foo"); - }, - "?max_id=foo&limit=42" - ); - qs_test!( - |request| { - request.limit(42).since_id("foo"); - }, - "?since_id=foo&limit=42" - ); - } -} |