paystack/http/
reqwest.rs

1use super::ReqwestError;
2use crate::http::base::Query;
3use crate::HttpClient;
4use async_trait::async_trait;
5use reqwest::{Client, Method, RequestBuilder};
6use serde_json::Value;
7use std::fmt::Debug;
8
9#[derive(Debug, Clone)]
10pub struct ReqwestClient {
11    /// An instance of the client to perform the http requests with
12    client: Client,
13}
14
15impl Default for ReqwestClient {
16    fn default() -> Self {
17        let client = reqwest::ClientBuilder::new().build().unwrap();
18
19        Self { client }
20    }
21}
22
23impl ReqwestClient {
24    async fn send_request<D: Fn(RequestBuilder) -> RequestBuilder>(
25        &self,
26        method: Method,
27        url: &str,
28        auth_key: &str,
29        add_data: D,
30    ) -> Result<String, ReqwestError> {
31        // configure the request object
32        let mut request = self
33            .client
34            .request(method.clone(), url)
35            .bearer_auth(auth_key)
36            .header("Content-Type", "application/json");
37
38        // Configure the request for the specific type (get/post/put/delete)
39        request = add_data(request);
40
41        // Performing the request
42        log::info!("Making request: {:?}", request);
43        let response = request.send().await?;
44
45        // Checking that we get a 200 range response
46        if response.status().is_success() {
47            response.text().await.map_err(Into::into)
48        } else {
49            Err(ReqwestError::StatusCode(response))
50        }
51    }
52}
53
54#[async_trait]
55impl HttpClient for ReqwestClient {
56    type Error = ReqwestError;
57
58    async fn get(
59        &self,
60        url: &str,
61        api_key: &str,
62        query: Option<&Query>,
63    ) -> Result<String, Self::Error> {
64        self.send_request(Method::GET, url, api_key, |req| {
65            if let Some(query) = query {
66                req.query(query)
67            } else {
68                req
69            }
70        })
71        .await
72    }
73
74    async fn post(&self, url: &str, api_key: &str, body: &Value) -> Result<String, Self::Error> {
75        self.send_request(Method::POST, url, api_key, |req| req.json(body))
76            .await
77    }
78
79    async fn put(&self, url: &str, api_key: &str, body: &Value) -> Result<String, Self::Error> {
80        self.send_request(Method::PUT, url, api_key, |req| req.json(body))
81            .await
82    }
83
84    async fn delete(&self, url: &str, api_key: &str, body: &Value) -> Result<String, Self::Error> {
85        self.send_request(Method::DELETE, url, api_key, |req| req.json(body))
86            .await
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    #[tokio::test]
94    async fn reqwest_client_cannot_get_unauthorized() {
95        // Set
96        let api_key = String::from("fake-key");
97        let url = "https://api.paystack.co/transaction/initialize";
98
99        // Run
100        let client = ReqwestClient::default();
101        let res = client.get(url, api_key.as_str(), None).await;
102
103        // Assert
104        // this should be a 401 error since we are not passing the right API key
105        assert!(res.is_err());
106        if let Err(e) = res {
107            match e {
108                ReqwestError::Reqwest(_) => {
109                    // don't need this error here
110                }
111                ReqwestError::StatusCode(code) => {
112                    assert_eq!(code.status(), 401);
113                }
114            }
115        }
116    }
117
118    #[tokio::test]
119    async fn reqwest_client_can_get() {
120        // Set
121        let api_key = "fake-key";
122        let url = "https://api.paystack.co/";
123
124        // Run
125        let client = ReqwestClient::default();
126        let res = client.get(url, api_key, None).await;
127
128        // Assert
129        assert!(res.is_ok());
130        match res {
131            Ok(res) => {
132                assert!(res.contains("true"))
133            }
134            Err(_) => {
135                // Not going to have an error here.
136            }
137        }
138    }
139}