use std::collections::HashMap;
use std::result::Result as StdResult;
use std::str::FromStr;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use serde_json;
use crate::common::*;
use crate::error::{
Error::{InvalidResponse, MissingValue},
Result,
};
use crate::{auth, entities, links};
pub async fn terms(token: &auth::Token) -> Result<Response<String>> {
let req = get(links::service::TERMS, token, None);
let ret = request_with_json_response::<serde_json::Value>(req).await?;
let tos = ret
.response
.get("tos")
.and_then(|tos| tos.as_str())
.map(String::from)
.ok_or_else(|| InvalidResponse("Missing field: tos", None))?;
Ok(Response::map(ret, |_| tos))
}
pub async fn privacy(token: &auth::Token) -> Result<Response<String>> {
let req = get(links::service::PRIVACY, token, None);
let ret = request_with_json_response::<serde_json::Value>(req).await?;
let privacy = ret
.response
.get("privacy")
.and_then(|tos| tos.as_str())
.map(String::from)
.ok_or_else(|| InvalidResponse("Missing field: privacy", None))?;
Ok(Response::map(ret, |_| privacy))
}
pub async fn config(token: &auth::Token) -> Result<Response<Configuration>> {
let req = get(links::service::CONFIG, token, None);
request_with_json_response(req).await
}
pub async fn rate_limit_status(token: &auth::Token) -> Result<Response<RateLimitStatus>> {
let req = get(links::service::RATE_LIMIT_STATUS, token, None);
request_with_json_response(req).await
}
#[doc(hidden)]
pub async fn rate_limit_status_raw(token: &auth::Token) -> Result<Response<serde_json::Value>> {
let req = get(links::service::RATE_LIMIT_STATUS, token, None);
request_with_json_response(req).await
}
#[derive(Debug, Deserialize)]
pub struct Configuration {
pub dm_text_character_limit: i32,
pub photo_sizes: entities::MediaSizes,
pub short_url_length: i32,
pub short_url_length_https: i32,
pub non_username_paths: Vec<String>,
}
#[derive(Debug)]
pub struct RateLimitStatus {
pub direct: HashMap<DirectMethod, Response<()>>,
pub place: HashMap<PlaceMethod, Response<()>>,
pub search: HashMap<SearchMethod, Response<()>>,
pub service: HashMap<ServiceMethod, Response<()>>,
pub tweet: HashMap<TweetMethod, Response<()>>,
pub user: HashMap<UserMethod, Response<()>>,
pub list: HashMap<ListMethod, Response<()>>,
}
impl<'de> Deserialize<'de> for RateLimitStatus {
fn deserialize<D>(ser: D) -> StdResult<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde_json::from_value;
let input = serde_json::Value::deserialize(ser)?;
let mut direct = HashMap::new();
let mut place = HashMap::new();
let mut search = HashMap::new();
let mut service = HashMap::new();
let mut tweet = HashMap::new();
let mut user = HashMap::new();
let mut list = HashMap::new();
let map = input
.get("resources")
.ok_or_else(|| D::Error::custom(MissingValue("resources")))?;
if let Some(map) = map.as_object() {
for (k, v) in map
.values()
.filter_map(|v| v.as_object())
.flat_map(|v| v.iter())
{
if let Ok(method) = k.parse::<Method>() {
match method {
Method::Direct(m) => {
direct.insert(m, from_value(v.clone()).map_err(D::Error::custom)?)
}
Method::Place(p) => {
place.insert(p, from_value(v.clone()).map_err(D::Error::custom)?)
}
Method::Search(s) => {
search.insert(s, from_value(v.clone()).map_err(D::Error::custom)?)
}
Method::Service(s) => {
service.insert(s, from_value(v.clone()).map_err(D::Error::custom)?)
}
Method::Tweet(t) => {
tweet.insert(t, from_value(v.clone()).map_err(D::Error::custom)?)
}
Method::User(u) => {
user.insert(u, from_value(v.clone()).map_err(D::Error::custom)?)
}
Method::List(l) => {
list.insert(l, from_value(v.clone()).map_err(D::Error::custom)?)
}
};
}
}
} else {
return Err(D::Error::custom(InvalidResponse(
"RateLimitStatus field 'resources' wasn't an object",
Some(input.to_string()),
)));
}
Ok(RateLimitStatus {
direct,
place,
search,
service,
tweet,
user,
list,
})
}
}
enum Method {
Direct(DirectMethod),
Place(PlaceMethod),
Search(SearchMethod),
Service(ServiceMethod),
Tweet(TweetMethod),
User(UserMethod),
List(ListMethod),
}
impl FromStr for Method {
type Err = ();
fn from_str(s: &str) -> StdResult<Self, ()> {
match s {
"/direct_messages" => Ok(Method::Direct(DirectMethod::Received)),
"/direct_messages/sent" => Ok(Method::Direct(DirectMethod::Sent)),
"/direct_messages/show" => Ok(Method::Direct(DirectMethod::Show)),
"/geo/search" => Ok(Method::Place(PlaceMethod::Search)),
"/geo/reverse_geocode" => Ok(Method::Place(PlaceMethod::ReverseGeocode)),
"/geo/id/:place_id" => Ok(Method::Place(PlaceMethod::Show)),
"/search/tweets" => Ok(Method::Search(SearchMethod::Search)),
"/help/configuration" => Ok(Method::Service(ServiceMethod::Config)),
"/help/privacy" => Ok(Method::Service(ServiceMethod::Privacy)),
"/help/tos" => Ok(Method::Service(ServiceMethod::Terms)),
"/account/verify_credentials" => Ok(Method::Service(ServiceMethod::VerifyTokens)),
"/application/rate_limit_status" => Ok(Method::Service(ServiceMethod::RateLimitStatus)),
"/statuses/mentions_timeline" => Ok(Method::Tweet(TweetMethod::MentionsTimeline)),
"/statuses/user_timeline" => Ok(Method::Tweet(TweetMethod::UserTimeline)),
"/statuses/home_timeline" => Ok(Method::Tweet(TweetMethod::HomeTimeline)),
"/statuses/retweets_of_me" => Ok(Method::Tweet(TweetMethod::RetweetsOfMe)),
"/statuses/retweets/:id" => Ok(Method::Tweet(TweetMethod::RetweetsOf)),
"/statuses/show/:id" => Ok(Method::Tweet(TweetMethod::Show)),
"/statuses/retweeters/ids" => Ok(Method::Tweet(TweetMethod::RetweetersOf)),
"/statuses/lookup" => Ok(Method::Tweet(TweetMethod::Lookup)),
"/favorites/list" => Ok(Method::Tweet(TweetMethod::LikedBy)),
"/users/show/:id" => Ok(Method::User(UserMethod::Show)),
"/users/lookup" => Ok(Method::User(UserMethod::Lookup)),
"/users/search" => Ok(Method::User(UserMethod::Search)),
"/friends/list" => Ok(Method::User(UserMethod::FriendsOf)),
"/friends/ids" => Ok(Method::User(UserMethod::FriendsIds)),
"/friendships/incoming" => Ok(Method::User(UserMethod::IncomingRequests)),
"/friendships/outgoing" => Ok(Method::User(UserMethod::OutgoingRequests)),
"/friendships/no_retweets/ids" => Ok(Method::User(UserMethod::FriendsNoRetweets)),
"/followers/list" => Ok(Method::User(UserMethod::FollowersOf)),
"/followers/ids" => Ok(Method::User(UserMethod::FollowersIds)),
"/blocks/list" => Ok(Method::User(UserMethod::Blocks)),
"/blocks/ids" => Ok(Method::User(UserMethod::BlocksIds)),
"/users/report_spam" => Ok(Method::User(UserMethod::ReportSpam)),
"/mutes/users/list" => Ok(Method::User(UserMethod::Mutes)),
"/mutes/users/ids" => Ok(Method::User(UserMethod::MutesIds)),
"/friendships/show" => Ok(Method::User(UserMethod::Relation)),
"/friendships/lookup" => Ok(Method::User(UserMethod::RelationLookup)),
"/lists/show" => Ok(Method::List(ListMethod::Show)),
"/lists/ownerships" => Ok(Method::List(ListMethod::Ownerships)),
"/lists/subscriptions" => Ok(Method::List(ListMethod::Subscriptions)),
"/lists/list" => Ok(Method::List(ListMethod::List)),
"/lists/members" => Ok(Method::List(ListMethod::Members)),
"/lists/memberships" => Ok(Method::List(ListMethod::Memberships)),
"/lists/members/show" => Ok(Method::List(ListMethod::IsMember)),
"/lists/subscribers" => Ok(Method::List(ListMethod::Subscribers)),
"/lists/subscribers/show" => Ok(Method::List(ListMethod::IsSubscribed)),
"/lists/statuses" => Ok(Method::List(ListMethod::Statuses)),
_ => Err(()),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum DirectMethod {
Show,
Sent,
Received,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum PlaceMethod {
Show,
Search,
ReverseGeocode,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum SearchMethod {
Search,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ServiceMethod {
Terms,
Privacy,
Config,
RateLimitStatus,
VerifyTokens,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum TweetMethod {
Show,
Lookup,
RetweetersOf,
RetweetsOf,
HomeTimeline,
MentionsTimeline,
UserTimeline,
RetweetsOfMe,
LikedBy,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum UserMethod {
Show,
Lookup,
FriendsNoRetweets,
Relation,
RelationLookup,
Search,
FriendsOf,
FriendsIds,
FollowersOf,
FollowersIds,
Blocks,
BlocksIds,
Mutes,
MutesIds,
IncomingRequests,
OutgoingRequests,
ReportSpam,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ListMethod {
Show,
Ownerships,
Subscriptions,
List,
Members,
Memberships,
IsMember,
Subscribers,
IsSubscribed,
Statuses,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::tests::load_file;
#[test]
fn parse_rate_limit() {
let sample = load_file("sample_payloads/rate_limit_sample.json");
::serde_json::from_str::<RateLimitStatus>(&sample).unwrap();
}
}