Skip to main content

rskit_httpclient/
request.rs

1//! HTTP request builder.
2
3use crate::auth::Auth;
4use http::Method;
5use std::collections::HashMap;
6
7/// HTTP request builder.
8#[derive(Debug, Clone)]
9pub struct Request {
10    /// HTTP method (GET, POST, etc.)
11    pub method: Method,
12
13    /// Request path (absolute or relative to base_url)
14    pub path: String,
15
16    /// Request headers
17    pub headers: HashMap<String, String>,
18
19    /// Request body (JSON or bytes)
20    pub body: Option<RequestBody>,
21
22    /// Query parameters
23    pub query: Option<HashMap<String, String>>,
24
25    /// Authentication override (if None, use client default)
26    pub auth: Option<Auth>,
27}
28
29/// Request body.
30#[derive(Debug, Clone)]
31#[non_exhaustive]
32pub enum RequestBody {
33    /// JSON body (serialized via serde_json)
34    Json(serde_json::Value),
35    /// Text body
36    Text(String),
37    /// Binary body
38    Bytes(bytes::Bytes),
39}
40
41impl RequestBody {
42    /// Creates a JSON body from a serializable value.
43    pub fn json<T: serde::Serialize>(value: &T) -> Result<Self, serde_json::Error> {
44        Ok(RequestBody::Json(serde_json::to_value(value)?))
45    }
46
47    /// Creates a text body.
48    pub fn text(text: impl Into<String>) -> Self {
49        RequestBody::Text(text.into())
50    }
51
52    /// Creates a binary body.
53    pub fn bytes(bytes: impl Into<bytes::Bytes>) -> Self {
54        RequestBody::Bytes(bytes.into())
55    }
56}
57
58impl Request {
59    /// Creates a new GET request.
60    pub fn get(path: impl Into<String>) -> Self {
61        Self::new(Method::GET, path)
62    }
63
64    /// Creates a new POST request.
65    pub fn post(path: impl Into<String>) -> Self {
66        Self::new(Method::POST, path)
67    }
68
69    /// Creates a new PUT request.
70    pub fn put(path: impl Into<String>) -> Self {
71        Self::new(Method::PUT, path)
72    }
73
74    /// Creates a new PATCH request.
75    pub fn patch(path: impl Into<String>) -> Self {
76        Self::new(Method::PATCH, path)
77    }
78
79    /// Creates a new DELETE request.
80    pub fn delete(path: impl Into<String>) -> Self {
81        Self::new(Method::DELETE, path)
82    }
83
84    /// Creates a new HEAD request.
85    pub fn head(path: impl Into<String>) -> Self {
86        Self::new(Method::HEAD, path)
87    }
88
89    /// Creates a new request with the given method and path.
90    pub fn new(method: Method, path: impl Into<String>) -> Self {
91        Self {
92            method,
93            path: path.into(),
94            headers: HashMap::new(),
95            body: None,
96            query: None,
97            auth: None,
98        }
99    }
100
101    /// Sets the request path.
102    pub fn path(mut self, path: impl Into<String>) -> Self {
103        self.path = path.into();
104        self
105    }
106
107    /// Adds a header.
108    pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
109        self.headers.insert(name.into(), value.into());
110        self
111    }
112
113    /// Sets multiple headers.
114    pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
115        self.headers = headers;
116        self
117    }
118
119    /// Sets the request body.
120    pub fn body(mut self, body: RequestBody) -> Self {
121        self.body = Some(body);
122        self
123    }
124
125    /// Sets the request body from a JSON-serializable value.
126    pub fn json_body<T: serde::Serialize>(mut self, value: &T) -> Result<Self, serde_json::Error> {
127        self.body = Some(RequestBody::json(value)?);
128        Ok(self)
129    }
130
131    /// Sets query parameters.
132    pub fn query(mut self, params: HashMap<String, String>) -> Self {
133        self.query = Some(params);
134        self
135    }
136
137    /// Adds a query parameter.
138    pub fn query_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
139        if self.query.is_none() {
140            self.query = Some(HashMap::new());
141        }
142        if let Some(ref mut q) = self.query {
143            q.insert(name.into(), value.into());
144        }
145        self
146    }
147
148    /// Sets authentication for this request (overrides client default).
149    pub fn auth(mut self, auth: Auth) -> Self {
150        self.auth = Some(auth);
151        self
152    }
153
154    /// Sets Bearer token authentication for this request.
155    pub fn bearer_token(self, token: impl Into<String>) -> Self {
156        self.auth(Auth::bearer(token))
157    }
158
159    /// Sets Basic authentication for this request.
160    pub fn basic_auth(self, username: impl Into<String>, password: impl Into<String>) -> Self {
161        self.auth(Auth::basic(username, password))
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_request_builder() {
171        let req = Request::get("/api/users")
172            .header("X-Custom", "value")
173            .query_param("limit", "10")
174            .bearer_token("secret-token");
175
176        assert_eq!(req.method, Method::GET);
177        assert_eq!(req.path, "/api/users");
178        assert_eq!(req.headers.get("X-Custom"), Some(&"value".to_string()));
179        assert_eq!(
180            req.query.as_ref().unwrap().get("limit"),
181            Some(&"10".to_string())
182        );
183        assert!(matches!(req.auth, Some(Auth::Bearer(_))));
184    }
185
186    #[test]
187    fn test_request_body_json() {
188        let body = RequestBody::json(&serde_json::json!({"key": "value"})).unwrap();
189        assert!(matches!(body, RequestBody::Json(_)));
190    }
191}