oxidite_testing/
request.rs1use http::{Method, HeaderMap, HeaderName, HeaderValue};
2use http_body_util::Full;
3use bytes::Bytes;
4use serde::Serialize;
5use oxidite_core::types::OxiditeRequest;
6use http_body_util::BodyExt;
7
8#[derive(Debug, thiserror::Error)]
10pub enum TestRequestError {
11 #[error("invalid header name: {0}")]
12 InvalidHeaderName(#[from] http::header::InvalidHeaderName),
13 #[error("invalid header value: {0}")]
14 InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
15 #[error("failed to serialize JSON body: {0}")]
16 JsonSerialize(#[from] serde_json::Error),
17 #[error("failed to build request: {0}")]
18 Build(#[from] http::Error),
19}
20
21pub struct TestRequest {
23 method: Method,
24 uri: String,
25 headers: HeaderMap,
26 body: Vec<u8>,
27}
28
29impl TestRequest {
30 pub fn new(method: Method, uri: impl Into<String>) -> Self {
32 Self {
33 method,
34 uri: uri.into(),
35 headers: HeaderMap::new(),
36 body: Vec::new(),
37 }
38 }
39
40 pub fn get(uri: impl Into<String>) -> Self {
42 Self::new(Method::GET, uri)
43 }
44
45 pub fn post(uri: impl Into<String>) -> Self {
47 Self::new(Method::POST, uri)
48 }
49
50 pub fn put(uri: impl Into<String>) -> Self {
52 Self::new(Method::PUT, uri)
53 }
54
55 pub fn delete(uri: impl Into<String>) -> Self {
57 Self::new(Method::DELETE, uri)
58 }
59
60 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
62 let name = HeaderName::from_bytes(name.into().as_bytes())
63 .expect("invalid header name in TestRequest::header");
64 let value = HeaderValue::from_str(&value.into())
65 .expect("invalid header value in TestRequest::header");
66 self.headers.insert(name, value);
67 self
68 }
69
70 pub fn try_header(
72 mut self,
73 name: impl Into<String>,
74 value: impl Into<String>,
75 ) -> Result<Self, TestRequestError> {
76 let name = HeaderName::from_bytes(name.into().as_bytes())?;
77 let value = HeaderValue::from_str(&value.into())?;
78 self.headers.insert(name, value);
79 Ok(self)
80 }
81
82 pub fn json<T: Serialize>(mut self, body: &T) -> Self {
84 self.body = serde_json::to_vec(body).expect("failed to serialize JSON body in TestRequest::json");
85 self = self.header("content-type", "application/json");
86 self
87 }
88
89 pub fn try_json<T: Serialize>(mut self, body: &T) -> Result<Self, TestRequestError> {
91 self.body = serde_json::to_vec(body)?;
92 self.try_header("content-type", "application/json")
93 }
94
95 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
97 self.body = body.into();
98 self
99 }
100
101 pub fn build(self) -> http::Request<Full<Bytes>> {
103 let mut builder = http::Request::builder()
104 .method(self.method)
105 .uri(self.uri);
106
107 for (name, value) in self.headers.iter() {
108 builder = builder.header(name, value);
109 }
110
111 builder
112 .body(Full::new(Bytes::from(self.body)))
113 .expect("failed to build http::Request in TestRequest::build")
114 }
115
116 pub fn try_build(self) -> Result<http::Request<Full<Bytes>>, TestRequestError> {
118 let mut builder = http::Request::builder()
119 .method(self.method)
120 .uri(self.uri);
121
122 for (name, value) in &self.headers {
123 builder = builder.header(name, value);
124 }
125
126 Ok(builder.body(Full::new(Bytes::from(self.body)))?)
127 }
128
129 pub fn build_oxidite(self) -> OxiditeRequest {
131 let request = self.build();
132 let (parts, body) = request.into_parts();
133 let body = body.map_err(|e| match e {}).boxed();
134 http::Request::from_parts(parts, body)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use serde::Deserialize;
142
143 #[derive(Serialize, Deserialize)]
144 struct TestData {
145 name: String,
146 }
147
148 #[test]
149 fn test_request_builder() {
150 let data = TestData {
151 name: "test".to_string(),
152 };
153
154 let request = TestRequest::post("/api/test")
155 .json(&data)
156 .header("x-custom", "value")
157 .build();
158
159 assert_eq!(request.method(), Method::POST);
160 assert_eq!(request.uri(), "/api/test");
161 assert!(request.headers().contains_key("content-type"));
162 }
163
164 #[test]
165 fn test_try_header_invalid_name() {
166 let result = TestRequest::get("/").try_header("bad header", "value");
167 assert!(result.is_err());
168 }
169
170 #[test]
171 fn test_build_oxidite() {
172 let request = TestRequest::get("/health").build_oxidite();
173 assert_eq!(request.uri(), "/health");
174 }
175}