use crate::auth::Auth;
use http::Method;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Request {
pub method: Method,
pub path: String,
pub headers: HashMap<String, String>,
pub body: Option<RequestBody>,
pub query: Option<HashMap<String, String>>,
pub auth: Option<Auth>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum RequestBody {
Json(serde_json::Value),
Text(String),
Bytes(bytes::Bytes),
}
impl RequestBody {
pub fn json<T: serde::Serialize>(value: &T) -> Result<Self, serde_json::Error> {
Ok(RequestBody::Json(serde_json::to_value(value)?))
}
pub fn text(text: impl Into<String>) -> Self {
RequestBody::Text(text.into())
}
pub fn bytes(bytes: impl Into<bytes::Bytes>) -> Self {
RequestBody::Bytes(bytes.into())
}
}
impl Request {
pub fn get(path: impl Into<String>) -> Self {
Self::new(Method::GET, path)
}
pub fn post(path: impl Into<String>) -> Self {
Self::new(Method::POST, path)
}
pub fn put(path: impl Into<String>) -> Self {
Self::new(Method::PUT, path)
}
pub fn patch(path: impl Into<String>) -> Self {
Self::new(Method::PATCH, path)
}
pub fn delete(path: impl Into<String>) -> Self {
Self::new(Method::DELETE, path)
}
pub fn head(path: impl Into<String>) -> Self {
Self::new(Method::HEAD, path)
}
pub fn new(method: Method, path: impl Into<String>) -> Self {
Self {
method,
path: path.into(),
headers: HashMap::new(),
body: None,
query: None,
auth: None,
}
}
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = path.into();
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(name.into(), value.into());
self
}
pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
self.headers = headers;
self
}
pub fn body(mut self, body: RequestBody) -> Self {
self.body = Some(body);
self
}
pub fn json_body<T: serde::Serialize>(mut self, value: &T) -> Result<Self, serde_json::Error> {
self.body = Some(RequestBody::json(value)?);
Ok(self)
}
pub fn query(mut self, params: HashMap<String, String>) -> Self {
self.query = Some(params);
self
}
pub fn query_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
if self.query.is_none() {
self.query = Some(HashMap::new());
}
if let Some(ref mut q) = self.query {
q.insert(name.into(), value.into());
}
self
}
pub fn auth(mut self, auth: Auth) -> Self {
self.auth = Some(auth);
self
}
pub fn bearer_token(self, token: impl Into<String>) -> Self {
self.auth(Auth::bearer(token))
}
pub fn basic_auth(self, username: impl Into<String>, password: impl Into<String>) -> Self {
self.auth(Auth::basic(username, password))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_builder() {
let req = Request::get("/api/users")
.header("X-Custom", "value")
.query_param("limit", "10")
.bearer_token("secret-token");
assert_eq!(req.method, Method::GET);
assert_eq!(req.path, "/api/users");
assert_eq!(req.headers.get("X-Custom"), Some(&"value".to_string()));
assert_eq!(
req.query.as_ref().unwrap().get("limit"),
Some(&"10".to_string())
);
assert!(matches!(req.auth, Some(Auth::Bearer(_))));
}
#[test]
fn test_request_body_json() {
let body = RequestBody::json(&serde_json::json!({"key": "value"})).unwrap();
assert!(matches!(body, RequestBody::Json(_)));
}
}