1use std::collections::HashMap;
38
39use axum::body::Body;
40use axum::http::Request;
41use http_body_util::BodyExt;
42use serde_json::{Map, Value};
43
44pub async fn fetch_post_data(req: Request<Body>) -> Result<Value, String> {
66 let query_map = parse_query(req.uri().query().unwrap_or(""));
68
69 let (parts, body) = req.into_parts();
71 let bytes = body
72 .collect()
73 .await
74 .map_err(|e| format!("read body failed: {e}"))?
75 .to_bytes();
76
77 let content_type = parts
79 .headers
80 .get(axum::http::header::CONTENT_TYPE)
81 .and_then(|v| v.to_str().ok())
82 .unwrap_or("")
83 .to_lowercase();
84
85 let mut result = Map::new();
86
87 for (k, v) in query_map {
89 result.insert(k, Value::String(v));
90 }
91
92 if !bytes.is_empty() {
94 if content_type.contains("application/json") {
95 let body_value: Value =
96 serde_json::from_slice(&bytes).map_err(|e| format!("invalid JSON body: {e}"))?;
97 if let Value::Object(body_map) = body_value {
98 for (k, v) in body_map {
99 result.insert(k, v);
100 }
101 } else {
102 result.insert("data".to_string(), body_value);
104 }
105 } else if content_type.contains("application/x-www-form-urlencoded") {
106 let body_str = String::from_utf8_lossy(&bytes);
107 let body_map = parse_query(&body_str);
108 for (k, v) in body_map {
109 result.insert(k, Value::String(v));
110 }
111 } else {
112 if let Ok(body_value) = serde_json::from_slice::<Value>(&bytes) {
114 if let Value::Object(body_map) = body_value {
115 for (k, v) in body_map {
116 result.insert(k, v);
117 }
118 } else {
119 result.insert("data".to_string(), body_value);
120 }
121 } else {
122 let raw = String::from_utf8_lossy(&bytes).to_string();
124 if !raw.is_empty() {
125 result.insert("data".to_string(), Value::String(raw));
126 }
127 }
128 }
129 }
130
131 Ok(Value::Object(result))
132}
133
134pub async fn fetch_post_data_by_key(
138 req: Request<Body>,
139 key: &str,
140) -> Result<Option<Value>, String> {
141 let data = fetch_post_data(req).await?;
142 Ok(data.get(key).cloned())
143}
144
145pub async fn fetch_body_data(req: Request<Body>) -> Result<Value, String> {
149 let (parts, body) = req.into_parts();
150 let bytes = body
151 .collect()
152 .await
153 .map_err(|e| format!("read body failed: {e}"))?
154 .to_bytes();
155
156 let content_type = parts
157 .headers
158 .get(axum::http::header::CONTENT_TYPE)
159 .and_then(|v| v.to_str().ok())
160 .unwrap_or("")
161 .to_lowercase();
162
163 let mut result = Map::new();
164
165 if bytes.is_empty() {
166 return Ok(Value::Object(result));
167 }
168
169 if content_type.contains("application/json") {
170 let body_value: Value =
171 serde_json::from_slice(&bytes).map_err(|e| format!("invalid JSON body: {e}"))?;
172 if let Value::Object(body_map) = body_value {
173 for (k, v) in body_map {
174 result.insert(k, v);
175 }
176 } else {
177 result.insert("data".to_string(), body_value);
178 }
179 } else if content_type.contains("application/x-www-form-urlencoded") {
180 let body_str = String::from_utf8_lossy(&bytes);
181 let body_map = parse_query(&body_str);
182 for (k, v) in body_map {
183 result.insert(k, Value::String(v));
184 }
185 } else if let Ok(body_value) = serde_json::from_slice::<Value>(&bytes) {
186 if let Value::Object(body_map) = body_value {
187 for (k, v) in body_map {
188 result.insert(k, v);
189 }
190 } else {
191 result.insert("data".to_string(), body_value);
192 }
193 } else {
194 let raw = String::from_utf8_lossy(&bytes).to_string();
195 result.insert("data".to_string(), Value::String(raw));
196 }
197
198 Ok(Value::Object(result))
199}
200
201pub fn fetch_query_data(req: &Request<Body>) -> Value {
205 let query_map = parse_query(req.uri().query().unwrap_or(""));
206 let mut result = Map::new();
207 for (k, v) in query_map {
208 result.insert(k, Value::String(v));
209 }
210 Value::Object(result)
211}
212
213pub fn fetch_query_data_by_key(req: &Request<Body>, key: &str) -> Option<Value> {
217 let query_map = parse_query(req.uri().query().unwrap_or(""));
218 query_map.get(key).map(|v| Value::String(v.clone()))
219}
220
221pub fn parse_query(query: &str) -> HashMap<String, String> {
225 let mut result = HashMap::new();
226 if query.is_empty() {
227 return result;
228 }
229
230 for pair in query.split('&') {
231 if pair.is_empty() {
232 continue;
233 }
234 let mut split = pair.splitn(2, '=');
235 let key = url_decode(split.next().unwrap_or(""));
236 let value = url_decode(split.next().unwrap_or(""));
237 result.insert(key, value);
238 }
239
240 result
241}
242
243pub fn url_decode(s: &str) -> String {
245 let mut result = String::with_capacity(s.len());
246 let mut chars = s.chars().peekable();
247
248 while let Some(c) = chars.next() {
249 match c {
250 '+' => result.push(' '),
251 '%' => {
252 let h1 = chars.next();
253 let h2 = chars.next();
254 if let (Some(a), Some(b)) = (h1, h2) {
255 if let Ok(byte) = u8::from_str_radix(&format!("{a}{b}"), 16) {
256 result.push(byte as char);
257 } else {
258 result.push('%');
259 result.push(a);
260 result.push(b);
261 }
262 } else {
263 result.push('%');
264 }
265 }
266 _ => result.push(c),
267 }
268 }
269
270 result
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use axum::http::{Method, Request, StatusCode};
277
278 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
279 let uri = match query {
280 Some(q) => format!("/?{q}"),
281 None => "/".to_string(),
282 };
283 Request::builder()
284 .method(Method::POST)
285 .uri(&uri)
286 .header("content-type", "application/json")
287 .body(Body::from(body.to_string()))
288 .unwrap()
289 }
290
291 fn make_form_request(body: &str, query: Option<&str>) -> Request<Body> {
292 let uri = match query {
293 Some(q) => format!("/?{q}"),
294 None => "/".to_string(),
295 };
296 Request::builder()
297 .method(Method::POST)
298 .uri(&uri)
299 .header("content-type", "application/x-www-form-urlencoded")
300 .body(Body::from(body.to_string()))
301 .unwrap()
302 }
303
304 #[test]
309 fn test_parse_query_empty() {
310 let m = parse_query("");
311 assert!(m.is_empty());
312 }
313
314 #[test]
315 fn test_parse_query_single_pair() {
316 let m = parse_query("key=value");
317 assert_eq!(m.get("key"), Some(&"value".to_string()));
318 assert_eq!(m.len(), 1);
319 }
320
321 #[test]
322 fn test_parse_query_multiple_pairs() {
323 let m = parse_query("a=1&b=2&c=3");
324 assert_eq!(m.get("a"), Some(&"1".to_string()));
325 assert_eq!(m.get("b"), Some(&"2".to_string()));
326 assert_eq!(m.get("c"), Some(&"3".to_string()));
327 }
328
329 #[test]
330 fn test_parse_query_no_value() {
331 let m = parse_query("key");
332 assert_eq!(m.get("key"), Some(&"".to_string()));
333 }
334
335 #[test]
336 fn test_parse_query_url_encoded() {
337 let m = parse_query("name=hello%20world&email=a%40b.com");
338 assert_eq!(m.get("name"), Some(&"hello world".to_string()));
339 assert_eq!(m.get("email"), Some(&"a@b.com".to_string()));
340 }
341
342 #[test]
343 fn test_parse_query_plus_for_space() {
344 let m = parse_query("q=hello+world");
345 assert_eq!(m.get("q"), Some(&"hello world".to_string()));
346 }
347
348 #[test]
349 fn test_parse_query_skip_empty_pairs() {
350 let m = parse_query("a=1&&b=2&");
351 assert_eq!(m.len(), 2);
352 assert_eq!(m.get("a"), Some(&"1".to_string()));
353 assert_eq!(m.get("b"), Some(&"2".to_string()));
354 }
355
356 #[test]
357 fn test_url_decode_basic() {
358 assert_eq!(url_decode("hello"), "hello");
359 assert_eq!(url_decode("hello%20world"), "hello world");
360 assert_eq!(url_decode("a%40b"), "a@b");
361 assert_eq!(url_decode("a+b"), "a b");
362 }
363
364 #[test]
365 fn test_url_decode_trailing_percent() {
366 assert_eq!(url_decode("100%"), "100%");
368 }
369
370 #[tokio::test]
375 async fn test_fetch_post_data_json_body_only() {
376 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
377 let data = fetch_post_data(req).await.unwrap();
378 assert_eq!(data["name"], "alice");
379 assert_eq!(data["age"], 30);
380 }
381
382 #[tokio::test]
383 async fn test_fetch_post_data_query_only() {
384 let req = make_json_request("", Some("page=1&size=10"));
385 let data = fetch_post_data(req).await.unwrap();
386 assert_eq!(data["page"], "1");
387 assert_eq!(data["size"], "10");
388 }
389
390 #[tokio::test]
391 async fn test_fetch_post_data_body_overrides_query() {
392 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
394 let data = fetch_post_data(req).await.unwrap();
395 assert_eq!(data["page"], 99); assert_eq!(data["size"], "10"); }
398
399 #[tokio::test]
400 async fn test_fetch_post_data_form_urlencoded() {
401 let req = make_form_request("name=bob&age=25", None);
402 let data = fetch_post_data(req).await.unwrap();
403 assert_eq!(data["name"], "bob");
404 assert_eq!(data["age"], "25");
405 }
406
407 #[tokio::test]
408 async fn test_fetch_post_data_empty_body() {
409 let req = make_json_request("", None);
410 let data = fetch_post_data(req).await.unwrap();
411 assert!(data.as_object().unwrap().is_empty());
412 }
413
414 #[tokio::test]
415 async fn test_fetch_post_data_invalid_json() {
416 let req = make_json_request("{invalid}", None);
417 let result = fetch_post_data(req).await;
418 assert!(result.is_err());
419 }
420
421 #[tokio::test]
422 async fn test_fetch_post_data_by_key() {
423 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
424 let name = fetch_post_data_by_key(req, "name").await.unwrap();
425 assert_eq!(name, Some(Value::String("alice".to_string())));
426 }
427
428 #[tokio::test]
429 async fn test_fetch_post_data_by_key_missing() {
430 let req = make_json_request(r#"{"name":"alice"}"#, None);
431 let age = fetch_post_data_by_key(req, "age").await.unwrap();
432 assert_eq!(age, None);
433 }
434
435 #[tokio::test]
436 async fn test_fetch_post_data_array_value_in_body() {
437 let req = make_json_request(r#"{"ids":[1,2,3]}"#, None);
438 let data = fetch_post_data(req).await.unwrap();
439 assert_eq!(data["ids"], serde_json::json!([1, 2, 3]));
440 }
441
442 #[tokio::test]
443 async fn test_fetch_post_data_nested_object_in_body() {
444 let req = make_json_request(r#"{"user":{"name":"alice","age":30}}"#, None);
445 let data = fetch_post_data(req).await.unwrap();
446 assert_eq!(data["user"]["name"], "alice");
447 assert_eq!(data["user"]["age"], 30);
448 }
449
450 #[tokio::test]
455 async fn test_fetch_body_data_json() {
456 let req = make_json_request(r#"{"name":"alice"}"#, Some("ignored=1"));
457 let data = fetch_body_data(req).await.unwrap();
458 assert_eq!(data["name"], "alice");
459 assert!(data.get("ignored").is_none());
461 }
462
463 #[tokio::test]
464 async fn test_fetch_body_data_empty() {
465 let req = make_json_request("", None);
466 let data = fetch_body_data(req).await.unwrap();
467 assert!(data.as_object().unwrap().is_empty());
468 }
469
470 #[test]
475 fn test_fetch_query_data_basic() {
476 let req = Request::builder()
477 .method(Method::GET)
478 .uri("/?page=1&size=10")
479 .body(Body::empty())
480 .unwrap();
481 let data = fetch_query_data(&req);
482 assert_eq!(data["page"], "1");
483 assert_eq!(data["size"], "10");
484 }
485
486 #[test]
487 fn test_fetch_query_data_no_query() {
488 let req = Request::builder()
489 .method(Method::GET)
490 .uri("/")
491 .body(Body::empty())
492 .unwrap();
493 let data = fetch_query_data(&req);
494 assert!(data.as_object().unwrap().is_empty());
495 }
496
497 #[test]
498 fn test_fetch_query_data_by_key_found() {
499 let req = Request::builder()
500 .method(Method::GET)
501 .uri("/?page=1&size=10")
502 .body(Body::empty())
503 .unwrap();
504 assert_eq!(
505 fetch_query_data_by_key(&req, "page"),
506 Some(Value::String("1".to_string()))
507 );
508 assert_eq!(
509 fetch_query_data_by_key(&req, "size"),
510 Some(Value::String("10".to_string()))
511 );
512 }
513
514 #[test]
515 fn test_fetch_query_data_by_key_not_found() {
516 let req = Request::builder()
517 .method(Method::GET)
518 .uri("/?page=1")
519 .body(Body::empty())
520 .unwrap();
521 assert_eq!(fetch_query_data_by_key(&req, "missing"), None);
522 }
523
524 #[tokio::test]
529 async fn test_post_data_via_axum_handler() {
530 use axum::routing::post;
531 use tower::ServiceExt;
532
533 async fn handler(req: Request<Body>) -> (StatusCode, String) {
534 let data = fetch_post_data(req).await.unwrap();
535 let name = data["name"].as_str().unwrap_or("unknown");
536 let age = data["age"].as_i64().unwrap_or(0);
537 (StatusCode::OK, format!("{name} is {age}"))
538 }
539
540 let router = axum::Router::new().route("/", post(handler));
541
542 let req = Request::builder()
543 .method(Method::POST)
544 .uri("/")
545 .header("content-type", "application/json")
546 .body(Body::from(r#"{"name":"alice","age":30}"#))
547 .unwrap();
548 let resp = router.oneshot(req).await.unwrap();
549 assert_eq!(resp.status(), StatusCode::OK);
550
551 use http_body_util::BodyExt;
552 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
553 assert_eq!(&bytes[..], b"alice is 30");
554 }
555
556 #[tokio::test]
569 async fn test_php_consistency_post_data_merges_body_and_query() {
570 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
574 let data = fetch_post_data(req).await.unwrap();
575 assert_eq!(data["page"], 99, "body 应覆盖 query 同名字段");
576 assert_eq!(data["size"], "10", "query 字段应保留");
577 }
578
579 #[tokio::test]
580 async fn test_php_consistency_post_data_form_urlencoded_body() {
581 let req = make_form_request("name=bob&age=25", None);
585 let data = fetch_post_data(req).await.unwrap();
586 assert_eq!(data["name"], "bob");
587 assert_eq!(data["age"], "25");
588 }
589
590 #[tokio::test]
591 async fn test_php_consistency_post_data_by_key_returns_value() {
592 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
596 let name = fetch_post_data_by_key(req, "name").await.unwrap();
597 assert_eq!(name, Some(Value::String("alice".to_string())));
598 }
599
600 #[test]
601 fn test_php_consistency_get_data_returns_only_query() {
602 let req = Request::builder()
606 .method(Method::GET)
607 .uri("/?page=1&size=10")
608 .body(Body::empty())
609 .unwrap();
610 let data = fetch_query_data(&req);
611 assert_eq!(data["page"], "1");
612 assert_eq!(data["size"], "10");
613 assert!(data.get("body").is_none());
615 }
616
617 #[test]
618 fn test_php_consistency_get_data_by_key_returns_query_value() {
619 let req = Request::builder()
623 .method(Method::GET)
624 .uri("/?page=1&size=10")
625 .body(Body::empty())
626 .unwrap();
627 assert_eq!(
628 fetch_query_data_by_key(&req, "page"),
629 Some(Value::String("1".to_string()))
630 );
631 assert_eq!(fetch_query_data_by_key(&req, "missing"), None);
632 }
633}