twitter-v2 0.1.8

Twitter API v2
Documentation
use crate::api::TwitterApi;
use crate::authorization::Authorization;
use crate::data::Expansions;
use crate::error::{Error, Result};
use crate::meta::PaginationMeta;
use crate::query::UrlQueryExt;
use async_trait::async_trait;
use reqwest::{Method, Response, StatusCode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::collections::HashMap;
use std::{fmt, ops};
use url::Url;

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ApiPayload<T, M> {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<T>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub meta: Option<M>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub includes: Option<Expansions>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub errors: Option<Vec<ApiError>>,
}

impl<T, M> ApiPayload<T, M> {
    pub fn data(&self) -> Option<&T> {
        self.data.as_ref()
    }
    pub fn meta(&self) -> Option<&M> {
        self.meta.as_ref()
    }
    pub fn includes(&self) -> Option<&Expansions> {
        self.includes.as_ref()
    }
    pub fn errors(&self) -> Option<&[ApiError]> {
        self.errors.as_deref()
    }
    pub fn into_data(self) -> Option<T> {
        self.data
    }
    pub fn into_meta(self) -> Option<M> {
        self.meta
    }
    pub fn into_includes(self) -> Option<Expansions> {
        self.includes
    }
    pub fn into_errors(self) -> Option<Vec<ApiError>> {
        self.errors
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ApiErrorItem {
    parameters: HashMap<String, Vec<serde_json::Value>>,
    message: String,
}

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct ApiError {
    pub title: String,
    #[serde(rename = "type")]
    pub kind: String,
    #[serde(default, with = "crate::utils::serde::status_code")]
    pub status: StatusCode,
    #[serde(default)]
    pub detail: String,
    #[serde(default)]
    pub errors: Vec<ApiErrorItem>,
}

impl fmt::Display for ApiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("[{}] {}", self.status, self.detail))
    }
}

impl std::error::Error for ApiError {}

#[derive(Debug)]
pub struct ApiResponse<A, T, M> {
    client: TwitterApi<A>,
    url: Url,
    payload: ApiPayload<T, M>,
}

impl<A, T, M> ApiResponse<A, T, M> {
    pub(crate) fn new(client: &TwitterApi<A>, url: Url, payload: ApiPayload<T, M>) -> Self {
        Self {
            client: client.clone(),
            url,
            payload,
        }
    }
    pub fn client(&self) -> &TwitterApi<A> {
        &self.client
    }
    pub fn url(&self) -> &Url {
        &self.url
    }
    pub fn payload(&self) -> &ApiPayload<T, M> {
        &self.payload
    }
    pub fn into_payload(self) -> ApiPayload<T, M> {
        self.payload
    }
    pub fn into_data(self) -> Option<T> {
        self.payload.data
    }
    pub fn into_meta(self) -> Option<M> {
        self.payload.meta
    }
    pub fn into_includes(self) -> Option<Expansions> {
        self.payload.includes
    }
    pub fn into_errors(self) -> Option<Vec<ApiError>> {
        self.payload.errors
    }
}

impl<A, T, M> ops::Deref for ApiResponse<A, T, M> {
    type Target = ApiPayload<T, M>;
    fn deref(&self) -> &Self::Target {
        &self.payload
    }
}

impl<A, T, M> Serialize for ApiResponse<A, T, M>
where
    T: Serialize,
    M: Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.payload.serialize(serializer)
    }
}

impl<A, T, M> Clone for ApiResponse<A, T, M>
where
    T: Clone,
    M: Clone,
{
    fn clone(&self) -> Self {
        Self {
            client: self.client.clone(),
            url: self.url.clone(),
            payload: self.payload.clone(),
        }
    }
}

#[async_trait]
pub trait PaginableApiResponse: Sized {
    async fn next_page(&self) -> Result<Option<Self>>;
    async fn previous_page(&self) -> Result<Option<Self>>;
}

#[async_trait]
impl<A, T, M> PaginableApiResponse for ApiResponse<A, T, M>
where
    A: Authorization + Send + Sync,
    T: DeserializeOwned + Send + Sync,
    M: PaginationMeta + DeserializeOwned + Send + Sync,
{
    async fn next_page(&self) -> Result<Option<Self>> {
        if let Some(token) = self.meta().and_then(|m| m.next_token()) {
            let mut url = self.url.clone();
            url.replace_query_val("pagination_token", token);
            Ok(Some(
                self.client
                    .send(self.client.request(Method::GET, url))
                    .await?,
            ))
        } else {
            Ok(None)
        }
    }
    async fn previous_page(&self) -> Result<Option<Self>> {
        if let Some(token) = self.meta().and_then(|m| m.previous_token()) {
            let mut url = self.url.clone();
            url.replace_query_val("pagination_token", token);
            Ok(Some(
                self.client
                    .send(self.client.request(Method::GET, url))
                    .await?,
            ))
        } else {
            Ok(None)
        }
    }
}

pub type ApiResult<A, T, M> = Result<ApiResponse<A, T, M>>;

#[async_trait]
pub(crate) trait ApiResponseExt: Sized {
    async fn api_error_for_status(self) -> Result<Self>;
}

#[async_trait]
impl ApiResponseExt for Response {
    async fn api_error_for_status(self) -> Result<Self> {
        let status = self.status();
        if status.is_success() {
            Ok(self)
        } else {
            let text = self.text().await?;
            Err(Error::Api(
                if let Ok(mut error) = serde_json::from_str::<ApiError>(&text) {
                    error.status = status;
                    error
                } else {
                    ApiError {
                        status,
                        detail: text,
                        ..Default::default()
                    }
                },
            ))
        }
    }
}