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 {
249 let mut bytes: Vec<u8> = Vec::with_capacity(s.len());
250 let mut chars = s.chars().peekable();
251
252 while let Some(c) = chars.next() {
253 match c {
254 '+' => bytes.push(b' '),
255 '%' => {
256 let h1 = chars.next();
257 let h2 = chars.next();
258 if let (Some(a), Some(b)) = (h1, h2) {
259 if let Ok(byte) = u8::from_str_radix(&format!("{a}{b}"), 16) {
260 bytes.push(byte);
261 } else {
262 bytes.push(b'%');
264 push_char_utf8(&mut bytes, a);
265 push_char_utf8(&mut bytes, b);
266 }
267 } else {
268 bytes.push(b'%');
269 }
270 }
271 _ => push_char_utf8(&mut bytes, c),
272 }
273 }
274
275 String::from_utf8_lossy(&bytes).into_owned()
276}
277
278fn push_char_utf8(bytes: &mut Vec<u8>, c: char) {
280 let mut buf = [0u8; 4];
281 bytes.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use axum::http::{Method, Request, StatusCode};
288
289 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
290 let uri = match query {
291 Some(q) => format!("/?{q}"),
292 None => "/".to_string(),
293 };
294 Request::builder()
295 .method(Method::POST)
296 .uri(&uri)
297 .header("content-type", "application/json")
298 .body(Body::from(body.to_string()))
299 .unwrap()
300 }
301
302 fn make_form_request(body: &str, query: Option<&str>) -> Request<Body> {
303 let uri = match query {
304 Some(q) => format!("/?{q}"),
305 None => "/".to_string(),
306 };
307 Request::builder()
308 .method(Method::POST)
309 .uri(&uri)
310 .header("content-type", "application/x-www-form-urlencoded")
311 .body(Body::from(body.to_string()))
312 .unwrap()
313 }
314
315 #[test]
320 fn test_parse_query_empty() {
321 let m = parse_query("");
322 assert!(m.is_empty());
323 }
324
325 #[test]
326 fn test_parse_query_single_pair() {
327 let m = parse_query("key=value");
328 assert_eq!(m.get("key"), Some(&"value".to_string()));
329 assert_eq!(m.len(), 1);
330 }
331
332 #[test]
333 fn test_parse_query_multiple_pairs() {
334 let m = parse_query("a=1&b=2&c=3");
335 assert_eq!(m.get("a"), Some(&"1".to_string()));
336 assert_eq!(m.get("b"), Some(&"2".to_string()));
337 assert_eq!(m.get("c"), Some(&"3".to_string()));
338 }
339
340 #[test]
341 fn test_parse_query_no_value() {
342 let m = parse_query("key");
343 assert_eq!(m.get("key"), Some(&"".to_string()));
344 }
345
346 #[test]
347 fn test_parse_query_url_encoded() {
348 let m = parse_query("name=hello%20world&email=a%40b.com");
349 assert_eq!(m.get("name"), Some(&"hello world".to_string()));
350 assert_eq!(m.get("email"), Some(&"a@b.com".to_string()));
351 }
352
353 #[test]
354 fn test_parse_query_plus_for_space() {
355 let m = parse_query("q=hello+world");
356 assert_eq!(m.get("q"), Some(&"hello world".to_string()));
357 }
358
359 #[test]
360 fn test_parse_query_skip_empty_pairs() {
361 let m = parse_query("a=1&&b=2&");
362 assert_eq!(m.len(), 2);
363 assert_eq!(m.get("a"), Some(&"1".to_string()));
364 assert_eq!(m.get("b"), Some(&"2".to_string()));
365 }
366
367 #[test]
368 fn test_url_decode_basic() {
369 assert_eq!(url_decode("hello"), "hello");
370 assert_eq!(url_decode("hello%20world"), "hello world");
371 assert_eq!(url_decode("a%40b"), "a@b");
372 assert_eq!(url_decode("a+b"), "a b");
373 }
374
375 #[test]
376 fn test_url_decode_utf8_multibyte() {
377 assert_eq!(url_decode("%E9%B2%9C%E8%A7%86%E8%BE%BE"), "鲜视达");
379 assert_eq!(url_decode("%E5%B7%A5%E5%85%B7%E7%AE%B1"), "工具箱");
380 assert_eq!(
382 url_decode("q=%E9%B2%9C%E8%A7%86%E8%BE%BE+plus"),
383 "q=鲜视达 plus"
384 );
385 }
386
387 #[test]
388 fn test_url_decode_invalid_utf8_lossy() {
389 let decoded = url_decode("%FF%FE");
391 assert!(decoded.contains('\u{FFFD}'));
392 }
393
394 #[test]
395 fn test_url_decode_trailing_percent() {
396 assert_eq!(url_decode("100%"), "100%");
398 }
399
400 #[tokio::test]
405 async fn test_fetch_post_data_json_body_only() {
406 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
407 let data = fetch_post_data(req).await.unwrap();
408 assert_eq!(data["name"], "alice");
409 assert_eq!(data["age"], 30);
410 }
411
412 #[tokio::test]
413 async fn test_fetch_post_data_query_only() {
414 let req = make_json_request("", Some("page=1&size=10"));
415 let data = fetch_post_data(req).await.unwrap();
416 assert_eq!(data["page"], "1");
417 assert_eq!(data["size"], "10");
418 }
419
420 #[tokio::test]
421 async fn test_fetch_post_data_body_overrides_query() {
422 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
424 let data = fetch_post_data(req).await.unwrap();
425 assert_eq!(data["page"], 99); assert_eq!(data["size"], "10"); }
428
429 #[tokio::test]
430 async fn test_fetch_post_data_form_urlencoded() {
431 let req = make_form_request("name=bob&age=25", None);
432 let data = fetch_post_data(req).await.unwrap();
433 assert_eq!(data["name"], "bob");
434 assert_eq!(data["age"], "25");
435 }
436
437 #[tokio::test]
438 async fn test_fetch_post_data_empty_body() {
439 let req = make_json_request("", None);
440 let data = fetch_post_data(req).await.unwrap();
441 assert!(data.as_object().unwrap().is_empty());
442 }
443
444 #[tokio::test]
445 async fn test_fetch_post_data_invalid_json() {
446 let req = make_json_request("{invalid}", None);
447 let result = fetch_post_data(req).await;
448 assert!(result.is_err());
449 }
450
451 #[tokio::test]
452 async fn test_fetch_post_data_by_key() {
453 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
454 let name = fetch_post_data_by_key(req, "name").await.unwrap();
455 assert_eq!(name, Some(Value::String("alice".to_string())));
456 }
457
458 #[tokio::test]
459 async fn test_fetch_post_data_by_key_missing() {
460 let req = make_json_request(r#"{"name":"alice"}"#, None);
461 let age = fetch_post_data_by_key(req, "age").await.unwrap();
462 assert_eq!(age, None);
463 }
464
465 #[tokio::test]
466 async fn test_fetch_post_data_array_value_in_body() {
467 let req = make_json_request(r#"{"ids":[1,2,3]}"#, None);
468 let data = fetch_post_data(req).await.unwrap();
469 assert_eq!(data["ids"], serde_json::json!([1, 2, 3]));
470 }
471
472 #[tokio::test]
473 async fn test_fetch_post_data_nested_object_in_body() {
474 let req = make_json_request(r#"{"user":{"name":"alice","age":30}}"#, None);
475 let data = fetch_post_data(req).await.unwrap();
476 assert_eq!(data["user"]["name"], "alice");
477 assert_eq!(data["user"]["age"], 30);
478 }
479
480 #[tokio::test]
485 async fn test_fetch_body_data_json() {
486 let req = make_json_request(r#"{"name":"alice"}"#, Some("ignored=1"));
487 let data = fetch_body_data(req).await.unwrap();
488 assert_eq!(data["name"], "alice");
489 assert!(data.get("ignored").is_none());
491 }
492
493 #[tokio::test]
494 async fn test_fetch_body_data_empty() {
495 let req = make_json_request("", None);
496 let data = fetch_body_data(req).await.unwrap();
497 assert!(data.as_object().unwrap().is_empty());
498 }
499
500 #[test]
505 fn test_fetch_query_data_basic() {
506 let req = Request::builder()
507 .method(Method::GET)
508 .uri("/?page=1&size=10")
509 .body(Body::empty())
510 .unwrap();
511 let data = fetch_query_data(&req);
512 assert_eq!(data["page"], "1");
513 assert_eq!(data["size"], "10");
514 }
515
516 #[test]
517 fn test_fetch_query_data_no_query() {
518 let req = Request::builder()
519 .method(Method::GET)
520 .uri("/")
521 .body(Body::empty())
522 .unwrap();
523 let data = fetch_query_data(&req);
524 assert!(data.as_object().unwrap().is_empty());
525 }
526
527 #[test]
528 fn test_fetch_query_data_by_key_found() {
529 let req = Request::builder()
530 .method(Method::GET)
531 .uri("/?page=1&size=10")
532 .body(Body::empty())
533 .unwrap();
534 assert_eq!(
535 fetch_query_data_by_key(&req, "page"),
536 Some(Value::String("1".to_string()))
537 );
538 assert_eq!(
539 fetch_query_data_by_key(&req, "size"),
540 Some(Value::String("10".to_string()))
541 );
542 }
543
544 #[test]
545 fn test_fetch_query_data_by_key_not_found() {
546 let req = Request::builder()
547 .method(Method::GET)
548 .uri("/?page=1")
549 .body(Body::empty())
550 .unwrap();
551 assert_eq!(fetch_query_data_by_key(&req, "missing"), None);
552 }
553
554 #[tokio::test]
559 async fn test_post_data_via_axum_handler() {
560 use axum::routing::post;
561 use tower::ServiceExt;
562
563 async fn handler(req: Request<Body>) -> (StatusCode, String) {
564 let data = fetch_post_data(req).await.unwrap();
565 let name = data["name"].as_str().unwrap_or("unknown");
566 let age = data["age"].as_i64().unwrap_or(0);
567 (StatusCode::OK, format!("{name} is {age}"))
568 }
569
570 let router = axum::Router::new().route("/", post(handler));
571
572 let req = Request::builder()
573 .method(Method::POST)
574 .uri("/")
575 .header("content-type", "application/json")
576 .body(Body::from(r#"{"name":"alice","age":30}"#))
577 .unwrap();
578 let resp = router.oneshot(req).await.unwrap();
579 assert_eq!(resp.status(), StatusCode::OK);
580
581 use http_body_util::BodyExt;
582 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
583 assert_eq!(&bytes[..], b"alice is 30");
584 }
585
586 #[tokio::test]
599 async fn test_php_consistency_post_data_merges_body_and_query() {
600 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
604 let data = fetch_post_data(req).await.unwrap();
605 assert_eq!(data["page"], 99, "body 应覆盖 query 同名字段");
606 assert_eq!(data["size"], "10", "query 字段应保留");
607 }
608
609 #[tokio::test]
610 async fn test_php_consistency_post_data_form_urlencoded_body() {
611 let req = make_form_request("name=bob&age=25", None);
615 let data = fetch_post_data(req).await.unwrap();
616 assert_eq!(data["name"], "bob");
617 assert_eq!(data["age"], "25");
618 }
619
620 #[tokio::test]
621 async fn test_php_consistency_post_data_by_key_returns_value() {
622 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
626 let name = fetch_post_data_by_key(req, "name").await.unwrap();
627 assert_eq!(name, Some(Value::String("alice".to_string())));
628 }
629
630 #[test]
631 fn test_php_consistency_get_data_returns_only_query() {
632 let req = Request::builder()
636 .method(Method::GET)
637 .uri("/?page=1&size=10")
638 .body(Body::empty())
639 .unwrap();
640 let data = fetch_query_data(&req);
641 assert_eq!(data["page"], "1");
642 assert_eq!(data["size"], "10");
643 assert!(data.get("body").is_none());
645 }
646
647 #[test]
648 fn test_php_consistency_get_data_by_key_returns_query_value() {
649 let req = Request::builder()
653 .method(Method::GET)
654 .uri("/?page=1&size=10")
655 .body(Body::empty())
656 .unwrap();
657 assert_eq!(
658 fetch_query_data_by_key(&req, "page"),
659 Some(Value::String("1".to_string()))
660 );
661 assert_eq!(fetch_query_data_by_key(&req, "missing"), None);
662 }
663}