pinpayments/client/
async_std.rs

1use std::future::{self};
2use futures::future::BoxFuture;
3
4use http_types::{Request};
5use serde::de::DeserializeOwned;
6
7use crate::error::{ErrorResponse, PinError};
8
9pub type Response<T> = BoxFuture<'static, Result<T, PinError>>;
10pub type StatusOnlyResponse = BoxFuture<'static, Result<u16, PinError>>;
11
12#[allow(dead_code)]
13#[inline(always)]
14pub(crate) fn ok<T: Send + 'static>(ok: T) -> Response<T> {
15    Box::pin(future::ready(Ok(ok)))
16}
17
18#[allow(dead_code)]
19#[inline(always)]
20pub(crate) fn err<T: Send + 'static>(err: PinError) -> Response<T> {
21    Box::pin(future::ready(Err(err)))
22}
23
24#[derive(Clone, Debug)]
25pub struct BaseClient {
26    client: surf::Client,
27}
28
29impl BaseClient {
30    pub fn new() -> Self {
31        Self { client: surf::Client::new() }
32    }
33
34    pub fn execute<T: DeserializeOwned + Send + 'static>(
35        &self,
36        request: Request,
37    ) -> Response<T> {
38        // As the client could be used across threads it is cloned.
39        // The client is send sync and cloned clients share the same pool.
40        let client = self.client.clone();
41
42        Box::pin(async move {
43            let bytes = send_inner(&client, request).await?;
44            let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
45            serde_path_to_error::deserialize(json_deserializer).map_err(PinError::from)
46        })
47    }
48
49    pub fn execute_status_only(
50        &self,
51        request: Request,
52    ) -> StatusOnlyResponse {
53
54        let client = self.client.clone();
55
56        Box::pin(async move {
57            Ok(send_inner_status_only(&client, request).await?)
58        })
59    }
60}
61
62async fn send_inner(
63    client: &surf::Client,
64    mut request: Request,
65) -> Result<Vec<u8>, PinError> {
66
67    let body = request.body_bytes().await?;
68
69    // clone the request before send so it can
70    // be re-used if the need to retry arises.
71    let mut request = request.clone();
72    request.set_body(body.clone());
73
74    let mut response = match client.send(request).await {
75        Ok(response) => {
76            response
77        },
78        Err(err) => { 
79            return Err(PinError::from(err))
80        }
81    };
82
83    let status = response.status();
84
85    let bytes = response.body_bytes().await?;
86
87    if !status.is_success() {
88        let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
89        let error = serde_path_to_error::deserialize(json_deserializer)
90            .map(|e: ErrorResponse| {
91                PinError::from(e)
92            })
93            .unwrap_or_else(PinError::from);
94
95            return Err(error)
96    }
97
98    Ok(bytes)
99}
100
101async fn send_inner_status_only(
102    client: &surf::Client,
103    mut request: Request,
104) -> Result<u16, PinError> {
105
106    let body = request.body_bytes().await?;
107
108    let mut request = request.clone();
109    request.set_body(body.clone());
110
111    let mut response = match client.send(request).await {
112        Ok(response) => {
113            response
114        },
115        Err(err) => {
116            return Err(PinError::from(err))
117        }
118    };
119
120    let status = response.status();
121
122    let bytes = response.body_bytes().await?;
123
124    if !status.is_success() {
125        let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
126        let error = serde_path_to_error::deserialize(json_deserializer)
127            .map(|e: ErrorResponse| {
128                PinError::from(e)
129            })
130            .unwrap_or_else(PinError::from);
131
132            return Err(error)
133    }
134
135    Ok(u16::from(status))
136}
137
138
139#[cfg(test)]
140mod tests {
141    use http_types::{Request, Url};
142    use httpmock::prelude::*;
143
144    use super::BaseClient;
145    use crate::{PinError};
146
147    #[async_std::test]
148    async fn user_error() {
149        let client = BaseClient::new();
150
151        let server = MockServer::start_async().await;
152
153        let mock = server.mock(|when, then| {
154            when.method(GET).path("/1/missing");
155            then.status(404).body("{
156                \"error\": \"not_found\",
157                \"error_description\": \"The requested resource was not found.\"
158              }
159              ");
160        });
161
162        let req = Request::get(Url::parse(&server.url("/1/missing")).unwrap());
163        let res = client.execute::<()>(req).await;
164
165        mock.assert_hits_async(1).await;
166
167        match res {
168            Err(PinError::PinPayments(x)) => println!("{:?}", x),
169            _ => panic!("Expected PinPayments error {:?}", res),
170        }
171    }
172}