twitter_v2/
api_result.rs

1use crate::api::TwitterApi;
2use crate::authorization::Authorization;
3use crate::data::Expansions;
4use crate::error::{Error, Result};
5use crate::meta::PaginationMeta;
6use crate::query::UrlQueryExt;
7use async_trait::async_trait;
8use reqwest::{Method, Response, StatusCode};
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10use std::collections::HashMap;
11use std::{fmt, ops};
12use url::Url;
13
14#[derive(Deserialize, Serialize, Debug, Clone)]
15pub struct ApiPayload<T, M> {
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub data: Option<T>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub meta: Option<M>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub includes: Option<Expansions>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub errors: Option<Vec<ApiError>>,
24}
25
26impl<T, M> ApiPayload<T, M> {
27    pub fn data(&self) -> Option<&T> {
28        self.data.as_ref()
29    }
30    pub fn meta(&self) -> Option<&M> {
31        self.meta.as_ref()
32    }
33    pub fn includes(&self) -> Option<&Expansions> {
34        self.includes.as_ref()
35    }
36    pub fn errors(&self) -> Option<&[ApiError]> {
37        self.errors.as_deref()
38    }
39    pub fn into_data(self) -> Option<T> {
40        self.data
41    }
42    pub fn into_meta(self) -> Option<M> {
43        self.meta
44    }
45    pub fn into_includes(self) -> Option<Expansions> {
46        self.includes
47    }
48    pub fn into_errors(self) -> Option<Vec<ApiError>> {
49        self.errors
50    }
51}
52
53#[derive(Deserialize, Serialize, Debug, Clone)]
54pub struct ApiErrorItem {
55    parameters: HashMap<String, Vec<serde_json::Value>>,
56    message: String,
57}
58
59#[derive(Deserialize, Serialize, Debug, Clone, Default)]
60pub struct ApiError {
61    pub title: String,
62    #[serde(rename = "type")]
63    pub kind: String,
64    #[serde(default, with = "crate::utils::serde::status_code")]
65    pub status: StatusCode,
66    #[serde(default)]
67    pub detail: String,
68    #[serde(default)]
69    pub errors: Vec<ApiErrorItem>,
70}
71
72impl fmt::Display for ApiError {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_fmt(format_args!("[{}] {}", self.status, self.detail))
75    }
76}
77
78impl std::error::Error for ApiError {}
79
80#[derive(Debug)]
81pub struct ApiResponse<A, T, M> {
82    client: TwitterApi<A>,
83    url: Url,
84    payload: ApiPayload<T, M>,
85}
86
87impl<A, T, M> ApiResponse<A, T, M> {
88    pub(crate) fn new(client: &TwitterApi<A>, url: Url, payload: ApiPayload<T, M>) -> Self {
89        Self {
90            client: client.clone(),
91            url,
92            payload,
93        }
94    }
95    pub fn client(&self) -> &TwitterApi<A> {
96        &self.client
97    }
98    pub fn url(&self) -> &Url {
99        &self.url
100    }
101    pub fn payload(&self) -> &ApiPayload<T, M> {
102        &self.payload
103    }
104    pub fn into_payload(self) -> ApiPayload<T, M> {
105        self.payload
106    }
107    pub fn into_data(self) -> Option<T> {
108        self.payload.data
109    }
110    pub fn into_meta(self) -> Option<M> {
111        self.payload.meta
112    }
113    pub fn into_includes(self) -> Option<Expansions> {
114        self.payload.includes
115    }
116    pub fn into_errors(self) -> Option<Vec<ApiError>> {
117        self.payload.errors
118    }
119}
120
121impl<A, T, M> ops::Deref for ApiResponse<A, T, M> {
122    type Target = ApiPayload<T, M>;
123    fn deref(&self) -> &Self::Target {
124        &self.payload
125    }
126}
127
128impl<A, T, M> Serialize for ApiResponse<A, T, M>
129where
130    T: Serialize,
131    M: Serialize,
132{
133    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134    where
135        S: serde::Serializer,
136    {
137        self.payload.serialize(serializer)
138    }
139}
140
141impl<A, T, M> Clone for ApiResponse<A, T, M>
142where
143    T: Clone,
144    M: Clone,
145{
146    fn clone(&self) -> Self {
147        Self {
148            client: self.client.clone(),
149            url: self.url.clone(),
150            payload: self.payload.clone(),
151        }
152    }
153}
154
155#[async_trait]
156pub trait PaginableApiResponse: Sized {
157    async fn next_page(&self) -> Result<Option<Self>>;
158    async fn previous_page(&self) -> Result<Option<Self>>;
159}
160
161#[async_trait]
162impl<A, T, M> PaginableApiResponse for ApiResponse<A, T, M>
163where
164    A: Authorization + Send + Sync,
165    T: DeserializeOwned + Send + Sync,
166    M: PaginationMeta + DeserializeOwned + Send + Sync,
167{
168    async fn next_page(&self) -> Result<Option<Self>> {
169        if let Some(token) = self.meta().and_then(|m| m.next_token()) {
170            let mut url = self.url.clone();
171            url.replace_query_val("pagination_token", token);
172            Ok(Some(
173                self.client
174                    .send(self.client.request(Method::GET, url))
175                    .await?,
176            ))
177        } else {
178            Ok(None)
179        }
180    }
181    async fn previous_page(&self) -> Result<Option<Self>> {
182        if let Some(token) = self.meta().and_then(|m| m.previous_token()) {
183            let mut url = self.url.clone();
184            url.replace_query_val("pagination_token", token);
185            Ok(Some(
186                self.client
187                    .send(self.client.request(Method::GET, url))
188                    .await?,
189            ))
190        } else {
191            Ok(None)
192        }
193    }
194}
195
196pub type ApiResult<A, T, M> = Result<ApiResponse<A, T, M>>;
197
198#[async_trait]
199pub(crate) trait ApiResponseExt: Sized {
200    async fn api_error_for_status(self) -> Result<Self>;
201}
202
203#[async_trait]
204impl ApiResponseExt for Response {
205    async fn api_error_for_status(self) -> Result<Self> {
206        let status = self.status();
207        if status.is_success() {
208            Ok(self)
209        } else {
210            let text = self.text().await?;
211            Err(Error::Api(
212                if let Ok(mut error) = serde_json::from_str::<ApiError>(&text) {
213                    error.status = status;
214                    error
215                } else {
216                    ApiError {
217                        status,
218                        detail: text,
219                        ..Default::default()
220                    }
221                },
222            ))
223        }
224    }
225}