ej_requests/
lib.rs

1//! HTTP client library for making API requests.
2//!
3//! This library provides a simplified wrapper around reqwest for making
4//! HTTP requests with JSON serialization/deserialization support.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use ej_requests::ApiClient;
10//!
11//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
12//! let client = ApiClient::new("https://api.example.com");
13//! let data: serde_json::Value = client.get("users").await;
14//! # Ok(())
15//! # }
16//! ```
17
18use std::{borrow::Borrow, error::Error, str::FromStr};
19
20use reqwest::{Response, StatusCode, Url, header};
21use serde::de::DeserializeOwned;
22
23/// HTTP client for making API requests with JSON support.
24pub struct ApiClient {
25    url: String,
26    pub client: reqwest::Client,
27}
28
29impl ApiClient {
30    /// Creates a new API client with the given base URL.
31    ///
32    /// # Examples
33    ///
34    /// ```rust
35    /// use ej_requests::ApiClient;
36    ///
37    /// let client = ApiClient::new("https://api.example.com");
38    /// ```
39    pub fn new(url: impl Into<String>) -> Self {
40        let mut headers = header::HeaderMap::new();
41        headers.insert(
42            "content-type",
43            header::HeaderValue::from_static("application/json"),
44        );
45        let client = reqwest::ClientBuilder::new()
46            .default_headers(headers)
47            .cookie_store(true)
48            .build()
49            .expect("Failed to build reqwest Client");
50        Self {
51            url: url.into(),
52            client,
53        }
54    }
55
56    /// Constructs the full URL path for an endpoint.
57    fn path(&self, endpoint: &str) -> String {
58        format!("{}/{endpoint}", self.url)
59    }
60
61    /// Makes a GET request to the specified URL and deserializes the response.
62    async fn get_url<T: DeserializeOwned>(url: Url) -> T {
63        serde_json::from_str(
64            &reqwest::get(url)
65                .await
66                .expect("Failed to send http request")
67                .text()
68                .await
69                .expect("Failed to get response text"),
70        )
71        .expect("Couldn't Parse Value")
72    }
73
74    /// Makes a GET request to the specified endpoint.
75    pub async fn get<T: DeserializeOwned>(&self, endpoint: &str) -> T {
76        let url = reqwest::Url::from_str(&self.path(&endpoint)).unwrap();
77        Self::get_url(url).await
78    }
79
80    /// Makes a GET request with query parameters.
81    pub async fn get_with_body<T, I, K, V>(&self, endpoint: &str, params: I) -> T
82    where
83        T: DeserializeOwned,
84        I: IntoIterator,
85        I::Item: Borrow<(K, V)>,
86        K: AsRef<str>,
87        V: AsRef<str>,
88    {
89        let url = reqwest::Url::parse_with_params(&self.path(&endpoint), params)
90            .expect("Couldn't create get request");
91        Self::get_url(url).await
92    }
93
94    /// Makes a POST request with the given body.
95    pub async fn post<T: Into<reqwest::Body>>(
96        &self,
97        endpoint: &str,
98        body: T,
99    ) -> Result<Response, Box<dyn Error>> {
100        let url = reqwest::Url::from_str(&self.path(endpoint)).unwrap();
101        Ok(self
102            .client
103            .post(url)
104            .header("content-type", "application/json")
105            .body(body)
106            .send()
107            .await?)
108    }
109
110    /// Makes a POST request and deserializes the response.
111    pub async fn post_and_deserialize<T: Into<reqwest::Body>, U: DeserializeOwned>(
112        &self,
113        endpoint: &str,
114        body: T,
115    ) -> Result<U, Box<dyn Error>> {
116        let url = reqwest::Url::from_str(&self.path(endpoint)).unwrap();
117
118        let response = self
119            .client
120            .post(url)
121            .header("content-type", "application/json")
122            .body(body)
123            .send()
124            .await?
125            .text()
126            .await?;
127
128        Ok(serde_json::from_str(&response)?)
129    }
130
131    /// Makes a POST request without a body and deserializes the response.
132    pub async fn post_no_body<T: DeserializeOwned>(
133        &self,
134        endpoint: &str,
135    ) -> Result<T, Box<dyn Error>> {
136        let url = reqwest::Url::from_str(&self.path(endpoint)).unwrap();
137
138        let response = self.client.post(url).send().await?.text().await?;
139
140        println!("Response {response}");
141        Ok(serde_json::from_str(&response)?)
142    }
143
144    /// Makes a DELETE request with query parameters.
145    pub async fn delete<I, K, V>(
146        &self,
147        client: &reqwest::Client,
148        endpoint: &str,
149        params: I,
150    ) -> StatusCode
151    where
152        I: IntoIterator,
153        I::Item: Borrow<(K, V)>,
154        K: AsRef<str>,
155        V: AsRef<str>,
156    {
157        let url = reqwest::Url::parse_with_params(&self.path(&endpoint), params)
158            .expect("Couldn't create get request");
159        client
160            .delete(url)
161            .header("content-type", "application/json")
162            .send()
163            .await
164            .expect("Failed to send patch request")
165            .status()
166    }
167}