use std::borrow::Cow;
use std::collections::HashMap;
use std::future::Future;
use std::iter::Peekable;
use std::pin::Pin;
use hyper::header::{HeaderMap, HeaderValue};
use percent_encoding::{utf8_percent_encode, AsciiSet, PercentEncode};
mod response;
pub use crate::auth::raw::{get, post, post_json};
pub use crate::common::response::*;
use crate::{error, list, user};
macro_rules! round_trip {
( $raw_name:path,
$(#[$outer_attr:meta])*
pub struct $struct_name:ident { $(
$(#[$attr:meta])*
$v:vis $f:ident : $t:ty
),+ $(,)? } ) => {
$(#[$outer_attr])*
#[derive(serde::Serialize)]
#[derive(serde::Deserialize)]
#[serde(try_from = "SerEnum")]
pub struct $struct_name { $(
$(#[$attr])*
$v $f: $t
),+ }
#[allow(unused_qualifications)]
impl crate::common::RoundTrip for $struct_name {
fn upstream_deser_error(input: serde_json::Value) -> Option<String> {
use crate::common::MapString;
serde_json::from_value::<$raw_name>(input).err().map_string()
}
fn roundtrip_deser_error(input: serde_json::Value) -> Option<String> {
use crate::common::MapString;
serde_json::from_value::<SerCopy>(input).err().map_string()
}
}
#[derive(serde::Deserialize)]
struct SerCopy { $(
$(#[$attr])*
$v $f: $t
),+ }
impl From<SerCopy> for $struct_name {
fn from(src: SerCopy) -> $struct_name {
$struct_name { $(
$f: src.$f
),+ }
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum SerEnum {
Raw($raw_name),
Ser(SerCopy),
}
#[allow(unused_qualifications)]
impl std::convert::TryFrom<SerEnum> for $struct_name
where
$struct_name: std::convert::TryFrom<$raw_name>,
{
type Error = <$struct_name as std::convert::TryFrom<$raw_name>>::Error;
fn try_from(src: SerEnum) -> std::result::Result<$struct_name, Self::Error> {
use std::convert::TryInto;
match src {
SerEnum::Raw(raw) => raw.try_into(),
SerEnum::Ser(ser) => Ok(ser.into()),
}
}
}
};
}
pub trait RoundTrip {
fn upstream_deser_error(input: serde_json::Value) -> Option<String>;
fn roundtrip_deser_error(input: serde_json::Value) -> Option<String>;
}
pub type Headers = HeaderMap<HeaderValue>;
pub type CowStr = Cow<'static, str>;
#[derive(Debug, Clone, Default, derive_more::Deref, derive_more::DerefMut, derive_more::From)]
pub struct ParamList(HashMap<Cow<'static, str>, Cow<'static, str>>);
impl ParamList {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn extended_tweets(self) -> Self {
self.add_param("tweet_mode", "extended")
}
pub fn add_param(
mut self,
key: impl Into<Cow<'static, str>>,
value: impl Into<Cow<'static, str>>,
) -> Self {
self.insert(key.into(), value.into());
self
}
pub fn add_opt_param(
self,
key: impl Into<Cow<'static, str>>,
value: Option<impl Into<Cow<'static, str>>>,
) -> Self {
match value {
Some(val) => self.add_param(key.into(), val.into()),
None => self,
}
}
pub fn add_param_ref(
&mut self,
key: impl Into<Cow<'static, str>>,
value: impl Into<Cow<'static, str>>,
) {
self.0.insert(key.into(), value.into());
}
pub fn add_user_param(self, id: user::UserID) -> Self {
match id {
user::UserID::ID(id) => self.add_param("user_id", id.to_string()),
user::UserID::ScreenName(name) => self.add_param("screen_name", name),
}
}
pub fn add_list_param(mut self, list: list::ListID) -> Self {
match list {
list::ListID::Slug(owner, name) => {
match owner {
user::UserID::ID(id) => {
self.add_param_ref("owner_id", id.to_string());
}
user::UserID::ScreenName(name) => {
self.add_param_ref("owner_screen_name", name);
}
}
self.add_param("slug", name.clone())
}
list::ListID::ID(id) => self.add_param("list_id", id.to_string()),
}
}
pub(crate) fn combine(&mut self, other: ParamList) {
self.0.extend(other.0);
}
pub fn to_urlencoded(&self) -> String {
self.0
.iter()
.map(|(k, v)| format!("{}={}", percent_encode(k), percent_encode(v)))
.collect::<Vec<_>>()
.join("&")
}
}
pub(crate) trait MapString {
fn map_string(&self) -> Option<String>;
}
impl<T: std::fmt::Display> MapString for Option<T> {
fn map_string(&self) -> Option<String> {
self.as_ref().map(|v| v.to_string())
}
}
pub fn multiple_names_param<T, I>(accts: I) -> (String, String)
where
T: Into<user::UserID>,
I: IntoIterator<Item = T>,
{
let mut ids = Vec::new();
let mut names = Vec::new();
for x in accts {
match x.into() {
user::UserID::ID(id) => ids.push(id.to_string()),
user::UserID::ScreenName(name) => names.push(name),
}
}
(ids.join(","), names.join(","))
}
pub(crate) type FutureResponse<T> =
Pin<Box<dyn Future<Output = error::Result<Response<T>>> + Send>>;
pub fn codepoints_to_bytes(&mut (ref mut start, ref mut end): &mut (usize, usize), text: &str) {
let mut byte_start = *start;
let mut byte_end = *end;
for (ch_offset, (by_offset, _)) in text.char_indices().enumerate() {
if ch_offset == *start {
byte_start = by_offset;
} else if ch_offset == *end {
byte_end = by_offset;
}
}
*start = byte_start;
if text.chars().count() == *end {
*end = text.len()
} else {
*end = byte_end
}
}
pub struct MergeBy<Iter, Fun>
where
Iter: Iterator,
{
left: Peekable<Iter>,
right: Peekable<Iter>,
comp: Fun,
fused: Option<bool>,
}
impl<Iter, Fun> Iterator for MergeBy<Iter, Fun>
where
Iter: Iterator,
Fun: FnMut(&Iter::Item, &Iter::Item) -> bool,
{
type Item = Iter::Item;
fn next(&mut self) -> Option<Self::Item> {
let is_left = match self.fused {
Some(lt) => lt,
None => match (self.left.peek(), self.right.peek()) {
(Some(a), Some(b)) => (self.comp)(a, b),
(Some(_), None) => {
self.fused = Some(true);
true
}
(None, Some(_)) => {
self.fused = Some(false);
false
}
(None, None) => return None,
},
};
if is_left {
self.left.next()
} else {
self.right.next()
}
}
}
pub mod serde_datetime {
use chrono::TimeZone;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
const DATE_FORMAT: &str = "%a %b %d %T %z %Y";
pub fn deserialize<'de, D>(ser: D) -> Result<chrono::DateTime<chrono::Utc>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(ser)?;
let date = (chrono::Utc)
.datetime_from_str(&s, DATE_FORMAT)
.map_err(D::Error::custom)?;
Ok(date)
}
pub fn serialize<S>(src: &chrono::DateTime<chrono::Utc>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ser.collect_str(&src.format(DATE_FORMAT))
}
}
pub mod serde_via_string {
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
use std::fmt;
pub fn deserialize<'de, D, T>(ser: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
let str = String::deserialize(ser)?;
str.parse().map_err(D::Error::custom)
}
pub fn serialize<T, S>(src: &T, ser: S) -> Result<S::Ok, S::Error>
where
T: fmt::Display,
S: Serializer,
{
ser.collect_str(src)
}
}
pub fn percent_encode(src: &str) -> PercentEncode {
lazy_static::lazy_static! {
static ref ENCODER: AsciiSet = percent_encoding::NON_ALPHANUMERIC.remove(b'-').remove(b'.').remove(b'_').remove(b'~');
}
utf8_percent_encode(src, &*ENCODER)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use std::fs::File;
use std::io::Read;
pub(crate) fn load_file(path: &str) -> String {
let mut file = File::open(path).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
content
}
#[test]
fn test_codepoints_to_bytes() {
let unicode = "frônt Iñtërnâtiônàližætiøn ënd";
let mut range = (6, 26);
codepoints_to_bytes(&mut range, unicode);
assert_eq!(&unicode[range.0..range.1], "Iñtërnâtiônàližætiøn");
let mut range = (6, 30);
codepoints_to_bytes(&mut range, unicode);
assert_eq!(&unicode[range.0..range.1], "Iñtërnâtiônàližætiøn ënd");
}
}