1use crate::error::{Error, Result};
2use reqwest::blocking::Response as ReqwestResponse;
3use reqwest::header::HeaderMap;
4use reqwest::StatusCode;
5use serde::de::DeserializeOwned;
6use serde_json::Value;
7use std::time::Duration;
8
9#[derive(Debug)]
10pub struct Response {
11 status: StatusCode,
12 headers: HeaderMap,
13 body: Vec<u8>,
14 duration: Duration,
15}
16
17impl Response {
18 pub(crate) fn from_reqwest(response: ReqwestResponse, duration: Duration) -> Result<Self> {
19 let status = response.status();
20 let headers = response.headers().clone();
21 let body = response.bytes()?.to_vec();
22
23 Ok(Self {
24 status,
25 headers,
26 body,
27 duration,
28 })
29 }
30
31 pub fn status(&self) -> u16 {
32 self.status.as_u16()
33 }
34
35 pub fn status_code(&self) -> StatusCode {
36 self.status
37 }
38
39 pub fn is_success(&self) -> bool {
40 self.status.is_success()
41 }
42
43 pub fn is_error(&self) -> bool {
44 self.status.is_client_error() || self.status.is_server_error()
45 }
46
47 pub fn headers(&self) -> &HeaderMap {
48 &self.headers
49 }
50
51 pub fn header(&self, key: &str) -> Option<&str> {
52 self.headers.get(key)?.to_str().ok()
53 }
54
55 pub fn duration(&self) -> Duration {
56 self.duration
57 }
58
59 pub fn body_bytes(&self) -> &[u8] {
60 &self.body
61 }
62
63 pub fn text(&self) -> Result<String> {
64 String::from_utf8(self.body.clone())
65 .map_err(|e| Error::Assertion(format!("Response body is not valid UTF-8: {}", e)))
66 }
67
68 pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
69 let text = self.text()?;
70 serde_json::from_str(&text).map_err(|e| e.into())
71 }
72
73 pub fn json_value(&self) -> Result<Value> {
74 self.json()
75 }
76
77 pub fn expect_status(self, expected: u16) -> Result<Self> {
79 let actual = self.status();
80 if actual != expected {
81 return Err(Error::StatusMismatch { expected, actual });
82 }
83 Ok(self)
84 }
85
86 pub fn expect_success(self) -> Result<Self> {
87 if !self.is_success() {
88 return Err(Error::Assertion(format!(
89 "Expected success status, got {}",
90 self.status()
91 )));
92 }
93 Ok(self)
94 }
95
96 pub fn expect_error(self) -> Result<Self> {
97 if !self.is_error() {
98 return Err(Error::Assertion(format!(
99 "Expected error status, got {}",
100 self.status()
101 )));
102 }
103 Ok(self)
104 }
105
106 pub fn expect_json(self) -> Result<Self> {
107 let content_type = self.header("content-type").unwrap_or("unknown");
108
109 if !content_type.contains("application/json") {
110 return Err(Error::NotJson(content_type.to_string()));
111 }
112
113 self.json_value()?;
114 Ok(self)
115 }
116
117 pub fn expect_text(self) -> Result<Self> {
118 self.text()?;
119 Ok(self)
120 }
121
122 pub fn expect_body_contains(self, text: &str) -> Result<Self> {
123 let body = self.text()?;
124 if !body.contains(text) {
125 return Err(Error::Assertion(format!(
126 "Expected body to contain '{}', but it didn't",
127 text
128 )));
129 }
130 Ok(self)
131 }
132
133 pub fn expect_header(self, key: &str, expected: &str) -> Result<Self> {
134 let actual = self
135 .header(key)
136 .ok_or_else(|| Error::Assertion(format!("Header '{}' not found", key)))?;
137
138 if actual != expected {
139 return Err(Error::HeaderMismatch {
140 key: key.to_string(),
141 expected: expected.to_string(),
142 actual: actual.to_string(),
143 });
144 }
145 Ok(self)
146 }
147
148 pub fn expect_content_type(self, content_type: &str) -> Result<Self> {
149 self.expect_header("content-type", content_type)
150 }
151
152 pub fn assert_field(self, path: &str, expected: impl Into<Value>) -> Result<Self> {
153 let json = self.json_value()?;
154 let expected_value = expected.into();
155
156 let actual_value = extract_json_path(&json, path).ok_or_else(|| Error::PathNotFound {
157 path: path.to_string(),
158 })?;
159
160 if actual_value != &expected_value {
161 return Err(Error::FieldMismatch {
162 field: path.to_string(),
163 expected: expected_value.to_string(),
164 actual: actual_value.to_string(),
165 });
166 }
167
168 Ok(self)
169 }
170
171 pub fn assert_field_exists(self, path: &str) -> Result<Self> {
172 let json = self.json_value()?;
173
174 extract_json_path(&json, path).ok_or_else(|| Error::PathNotFound {
175 path: path.to_string(),
176 })?;
177
178 Ok(self)
179 }
180
181 pub fn assert_array_length(self, path: &str, expected_length: usize) -> Result<Self> {
182 let json = self.json_value()?;
183
184 let array = extract_json_path(&json, path)
185 .and_then(|v| v.as_array())
186 .ok_or_else(|| Error::Assertion(format!("Path '{}' is not an array", path)))?;
187
188 if array.len() != expected_length {
189 return Err(Error::Assertion(format!(
190 "Array at '{}' expected length {}, got {}",
191 path,
192 expected_length,
193 array.len()
194 )));
195 }
196
197 Ok(self)
198 }
199}
200
201fn extract_json_path<'a>(value: &'a Value, path: &str) -> Option<&'a Value> {
202 let parts: Vec<&str> = path.split('.').collect();
203 let mut current = value;
204
205 for part in parts {
206 if let Some(index_start) = part.find('[') {
207 let field = &part[..index_start];
208 let index_str = &part[index_start + 1..part.len() - 1];
209 let index: usize = index_str.parse().ok()?;
210
211 current = current.get(field)?.get(index)?;
212 } else {
213 current = current.get(part)?;
214 }
215 }
216
217 Some(current)
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use serde_json::json;
224
225 #[test]
226 fn test_json_path_extraction() {
227 let json = json!({
228 "user": {
229 "name": "John",
230 "age": 30
231 },
232 "items": [
233 {"id": 1, "name": "First"},
234 {"id": 2, "name": "Second"}
235 ]
236 });
237
238 assert_eq!(extract_json_path(&json, "user.name"), Some(&json!("John")));
239 assert_eq!(extract_json_path(&json, "user.age"), Some(&json!(30)));
240 assert_eq!(
241 extract_json_path(&json, "items[0].name"),
242 Some(&json!("First"))
243 );
244 assert_eq!(extract_json_path(&json, "items[1].id"), Some(&json!(2)));
245 assert_eq!(extract_json_path(&json, "nonexistent"), None);
246 }
247}