1use crate::errors::{HttpError, HttpResult};
4use crate::request::ElifRequest;
5use serde::de::DeserializeOwned;
6use std::collections::HashMap;
7use std::str::FromStr;
8
9#[derive(Debug)]
11pub struct ElifQuery<T>(pub T);
12
13impl<T: DeserializeOwned> ElifQuery<T> {
14 pub fn from_request(request: &ElifRequest) -> HttpResult<Self> {
16 let query_str = request.query_string().unwrap_or("");
17 let data = serde_urlencoded::from_str::<T>(query_str)
18 .map_err(|e| HttpError::bad_request(format!("Invalid query parameters: {}", e)))?;
19 Ok(ElifQuery(data))
20 }
21}
22
23#[derive(Debug)]
25pub struct ElifPath<T>(pub T);
26
27impl<T: DeserializeOwned> ElifPath<T> {
28 pub fn from_request(request: &ElifRequest) -> HttpResult<Self> {
30 let data = request.path_params::<T>()?;
31 Ok(ElifPath(data))
32 }
33}
34
35#[derive(Debug)]
37pub struct ElifState<T>(pub T);
38
39impl<T: Clone> ElifState<T> {
40 pub fn new(state: T) -> Self {
42 ElifState(state)
43 }
44
45 pub fn inner(&self) -> &T {
47 &self.0
48 }
49
50 pub fn into_inner(self) -> T {
52 self.0
53 }
54}
55
56impl ElifRequest {
58 pub fn input<T>(&self, key: &str, default: T) -> T
65 where
66 T: FromStr + Clone,
67 T::Err: std::fmt::Debug,
68 {
69 self.query_param(key)
70 .or_else(|| self.path_param(key))
71 .and_then(|s| s.parse().ok())
72 .unwrap_or(default)
73 }
74
75 pub fn input_optional<T>(&self, key: &str) -> Option<T>
79 where
80 T: FromStr,
81 T::Err: std::fmt::Debug,
82 {
83 self.query_param(key)
84 .or_else(|| self.path_param(key))
85 .and_then(|s| s.parse().ok())
86 }
87
88 pub fn string(&self, key: &str, default: &str) -> String {
92 self.query_param(key)
93 .or_else(|| self.path_param(key))
94 .cloned()
95 .unwrap_or_else(|| default.to_string())
96 }
97
98 pub fn string_optional(&self, key: &str) -> Option<String> {
102 self.query_param(key)
103 .or_else(|| self.path_param(key))
104 .cloned()
105 }
106
107 pub fn integer(&self, key: &str, default: i64) -> i64 {
111 self.input(key, default)
112 }
113
114 pub fn integer_optional(&self, key: &str) -> Option<i64> {
118 self.input_optional(key)
119 }
120
121 pub fn boolean(&self, key: &str, default: bool) -> bool {
126 self.query_param(key)
127 .or_else(|| self.path_param(key))
128 .map(|s| match s.to_lowercase().as_str() {
129 "true" | "1" | "on" | "yes" => true,
130 "false" | "0" | "off" | "no" => false,
131 _ => default,
132 })
133 .unwrap_or(default)
134 }
135
136 pub fn inputs(&self, keys: &[&str]) -> HashMap<String, String> {
140 keys.iter()
141 .filter_map(|&key| {
142 self.query_param(key)
143 .or_else(|| self.path_param(key))
144 .map(|val| (key.to_string(), val.clone()))
145 })
146 .collect()
147 }
148
149 pub fn all_query(&self) -> HashMap<String, String> {
153 self.query_params
154 .iter()
155 .map(|(k, v)| (k.clone(), v.clone()))
156 .collect()
157 }
158
159 pub fn has(&self, key: &str) -> bool {
163 self.query_param(key).is_some() || self.path_param(key).is_some()
164 }
165
166 pub fn filled(&self, key: &str) -> bool {
170 self.query_param(key)
171 .or_else(|| self.path_param(key))
172 .map(|s| !s.trim().is_empty())
173 .unwrap_or(false)
174 }
175
176 pub fn array(&self, key: &str) -> Vec<String> {
180 if let Some(value) = self.query_param(key).or_else(|| self.path_param(key)) {
181 value
183 .split(',')
184 .map(|s| s.trim().to_string())
185 .filter(|s| !s.is_empty())
186 .collect()
187 } else {
188 Vec::new()
189 }
190 }
191
192 pub fn pagination(&self) -> (u32, u32) {
197 let page = self.input("page", 1u32).max(1);
198 let per_page = self.input("per_page", 10u32).clamp(1, 100);
199 (page, per_page)
200 }
201
202 pub fn sorting(&self, default_field: &str) -> (String, String) {
207 let sort = self.string("sort", default_field);
208 let order = self.string("order", "asc");
209 let direction = match order.to_lowercase().as_str() {
210 "desc" | "descending" | "down" => "desc".to_string(),
211 _ => "asc".to_string(),
212 };
213 (sort, direction)
214 }
215
216 pub fn filters(&self) -> HashMap<String, String> {
221 self.inputs(&[
222 "search", "q", "query", "status", "state", "category", "type", "filter", "filters",
223 ])
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::request::ElifRequest;
231 use serde::Deserialize;
232
233 #[derive(Debug, Deserialize, PartialEq)]
234 struct TestQuery {
235 name: String,
236 age: Option<u32>,
237 active: Option<bool>,
238 }
239
240 #[derive(Debug, Deserialize, PartialEq)]
241 struct TestPath {
242 id: u32,
243 slug: String,
244 }
245
246 #[derive(Debug, Clone, PartialEq)]
247 struct TestAppState {
248 database_url: String,
249 api_key: String,
250 }
251
252 fn create_test_request_with_query(query: &str) -> ElifRequest {
253 use axum::body::Body;
254 use axum::extract::Request;
255
256 let uri = if query.is_empty() {
257 "/test".to_string()
258 } else {
259 format!("/test?{}", query)
260 };
261
262 let request = Request::builder()
263 .method("GET")
264 .uri(uri)
265 .body(Body::empty())
266 .unwrap();
267
268 let (parts, _body) = request.into_parts();
269 ElifRequest::extract_elif_request(
270 crate::request::ElifMethod::from_axum(parts.method),
271 parts.uri,
272 crate::response::headers::ElifHeaderMap::from_axum(parts.headers),
273 None,
274 )
275 }
276
277 #[test]
278 fn test_elif_query_extraction_success() {
279 let request = create_test_request_with_query("name=John&age=30&active=true");
280 let result: Result<ElifQuery<TestQuery>, _> = ElifQuery::from_request(&request);
281
282 assert!(result.is_ok());
283 let query = result.unwrap();
284 assert_eq!(query.0.name, "John");
285 assert_eq!(query.0.age, Some(30));
286 assert_eq!(query.0.active, Some(true));
287 }
288
289 #[test]
290 fn test_elif_query_extraction_partial() {
291 let request = create_test_request_with_query("name=Alice");
292 let result: Result<ElifQuery<TestQuery>, _> = ElifQuery::from_request(&request);
293
294 assert!(result.is_ok());
295 let query = result.unwrap();
296 assert_eq!(query.0.name, "Alice");
297 assert_eq!(query.0.age, None);
298 assert_eq!(query.0.active, None);
299 }
300
301 #[test]
302 fn test_elif_query_extraction_empty() {
303 let request = create_test_request_with_query("");
304 let result: Result<ElifQuery<TestQuery>, _> = ElifQuery::from_request(&request);
305
306 assert!(result.is_err());
308 assert!(matches!(result.unwrap_err(), HttpError::BadRequest { .. }));
309 }
310
311 #[test]
312 fn test_elif_query_extraction_invalid_format() {
313 let request = create_test_request_with_query("name=John&age=not_a_number");
314 let result: Result<ElifQuery<TestQuery>, _> = ElifQuery::from_request(&request);
315
316 assert!(result.is_err());
317 assert!(matches!(result.unwrap_err(), HttpError::BadRequest { .. }));
318 }
319
320 #[test]
321 fn test_elif_query_url_decoding() {
322 let request = create_test_request_with_query("name=John%20Doe&active=true");
323 let result: Result<ElifQuery<TestQuery>, _> = ElifQuery::from_request(&request);
324
325 assert!(result.is_ok());
326 let query = result.unwrap();
327 assert_eq!(query.0.name, "John Doe");
328 }
329
330 #[test]
331 fn test_elif_state_creation_and_access() {
332 let state = TestAppState {
333 database_url: "postgres://localhost:5432/test".to_string(),
334 api_key: "secret_key_123".to_string(),
335 };
336
337 let elif_state = ElifState::new(state.clone());
338
339 assert_eq!(elif_state.inner(), &state);
341
342 let recovered_state = elif_state.into_inner();
344 assert_eq!(recovered_state, state);
345 }
346
347 #[test]
348 fn test_elif_state_clone_requirement() {
349 #[derive(Clone, PartialEq, Debug)]
350 struct CloneableState {
351 value: i32,
352 }
353
354 let state = CloneableState { value: 42 };
355 let elif_state = ElifState::new(state.clone());
356
357 assert_eq!(elif_state.inner().value, 42);
358 assert_eq!(elif_state.into_inner(), state);
359 }
360
361 #[test]
362 fn test_elif_query_debug_impl() {
363 let query = ElifQuery(TestQuery {
364 name: "Test".to_string(),
365 age: Some(25),
366 active: Some(false),
367 });
368
369 let debug_string = format!("{:?}", query);
370 assert!(debug_string.contains("ElifQuery"));
371 assert!(debug_string.contains("Test"));
372 }
373
374 #[test]
375 fn test_elif_path_debug_impl() {
376 let path = ElifPath(TestPath {
377 id: 123,
378 slug: "test-slug".to_string(),
379 });
380
381 let debug_string = format!("{:?}", path);
382 assert!(debug_string.contains("ElifPath"));
383 assert!(debug_string.contains("123"));
384 assert!(debug_string.contains("test-slug"));
385 }
386
387 #[test]
388 fn test_elif_state_debug_impl() {
389 let state = ElifState::new(TestAppState {
390 database_url: "postgres://localhost:5432/test".to_string(),
391 api_key: "secret_key_123".to_string(),
392 });
393
394 let debug_string = format!("{:?}", state);
395 assert!(debug_string.contains("ElifState"));
396 }
397}