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 const MAX_POST_BODY_BYTES: usize = 1024 * 1024; pub async fn fetch_post_data(req: Request<Body>) -> Result<Value, String> {
76 let query_map = parse_query(req.uri().query().unwrap_or(""));
78
79 let (parts, body) = req.into_parts();
81 let bytes = axum::body::to_bytes(body, MAX_POST_BODY_BYTES)
82 .await
83 .map_err(|e| format!("read body failed: {e}"))?;
84
85 let content_type = parts
87 .headers
88 .get(axum::http::header::CONTENT_TYPE)
89 .and_then(|v| v.to_str().ok())
90 .unwrap_or("")
91 .to_lowercase();
92
93 let mut result = Map::new();
94
95 for (k, v) in query_map {
97 result.insert(k, Value::String(v));
98 }
99
100 if !bytes.is_empty() {
102 if content_type.contains("application/json") {
103 let body_value: Value =
104 serde_json::from_slice(&bytes).map_err(|e| format!("invalid JSON body: {e}"))?;
105 if let Value::Object(body_map) = body_value {
106 for (k, v) in body_map {
107 result.insert(k, v);
108 }
109 } else {
110 result.insert("data".to_string(), body_value);
112 }
113 } else if content_type.contains("application/x-www-form-urlencoded") {
114 let body_str = String::from_utf8_lossy(&bytes);
115 let body_map = parse_query(&body_str);
116 for (k, v) in body_map {
117 result.insert(k, Value::String(v));
118 }
119 } else {
120 if let Ok(body_value) = serde_json::from_slice::<Value>(&bytes) {
122 if let Value::Object(body_map) = body_value {
123 for (k, v) in body_map {
124 result.insert(k, v);
125 }
126 } else {
127 result.insert("data".to_string(), body_value);
128 }
129 } else {
130 let raw = String::from_utf8_lossy(&bytes).to_string();
132 if !raw.is_empty() {
133 result.insert("data".to_string(), Value::String(raw));
134 }
135 }
136 }
137 }
138
139 Ok(Value::Object(result))
140}
141
142pub async fn fetch_post_data_by_key(
146 req: Request<Body>,
147 key: &str,
148) -> Result<Option<Value>, String> {
149 let data = fetch_post_data(req).await?;
150 Ok(data.get(key).cloned())
151}
152
153pub async fn fetch_body_data(req: Request<Body>) -> Result<Value, String> {
157 let (parts, body) = req.into_parts();
158 let bytes = body
159 .collect()
160 .await
161 .map_err(|e| format!("read body failed: {e}"))?
162 .to_bytes();
163
164 let content_type = parts
165 .headers
166 .get(axum::http::header::CONTENT_TYPE)
167 .and_then(|v| v.to_str().ok())
168 .unwrap_or("")
169 .to_lowercase();
170
171 let mut result = Map::new();
172
173 if bytes.is_empty() {
174 return Ok(Value::Object(result));
175 }
176
177 if content_type.contains("application/json") {
178 let body_value: Value =
179 serde_json::from_slice(&bytes).map_err(|e| format!("invalid JSON body: {e}"))?;
180 if let Value::Object(body_map) = body_value {
181 for (k, v) in body_map {
182 result.insert(k, v);
183 }
184 } else {
185 result.insert("data".to_string(), body_value);
186 }
187 } else if content_type.contains("application/x-www-form-urlencoded") {
188 let body_str = String::from_utf8_lossy(&bytes);
189 let body_map = parse_query(&body_str);
190 for (k, v) in body_map {
191 result.insert(k, Value::String(v));
192 }
193 } else if let Ok(body_value) = serde_json::from_slice::<Value>(&bytes) {
194 if let Value::Object(body_map) = body_value {
195 for (k, v) in body_map {
196 result.insert(k, v);
197 }
198 } else {
199 result.insert("data".to_string(), body_value);
200 }
201 } else {
202 let raw = String::from_utf8_lossy(&bytes).to_string();
203 result.insert("data".to_string(), Value::String(raw));
204 }
205
206 Ok(Value::Object(result))
207}
208
209pub fn fetch_query_data(req: &Request<Body>) -> Value {
213 let query_map = parse_query(req.uri().query().unwrap_or(""));
214 let mut result = Map::new();
215 for (k, v) in query_map {
216 result.insert(k, Value::String(v));
217 }
218 Value::Object(result)
219}
220
221pub fn fetch_query_data_by_key(req: &Request<Body>, key: &str) -> Option<Value> {
225 let query_map = parse_query(req.uri().query().unwrap_or(""));
226 query_map.get(key).map(|v| Value::String(v.clone()))
227}
228
229pub fn parse_query(query: &str) -> HashMap<String, String> {
233 let mut result = HashMap::new();
234 if query.is_empty() {
235 return result;
236 }
237
238 for pair in query.split('&') {
239 if pair.is_empty() {
240 continue;
241 }
242 let mut split = pair.splitn(2, '=');
243 let key = url_decode(split.next().unwrap_or(""));
244 let value = url_decode(split.next().unwrap_or(""));
245 result.insert(key, value);
246 }
247
248 result
249}
250
251pub fn url_decode(s: &str) -> String {
257 let mut bytes: Vec<u8> = Vec::with_capacity(s.len());
258 let mut chars = s.chars().peekable();
259
260 while let Some(c) = chars.next() {
261 match c {
262 '+' => bytes.push(b' '),
263 '%' => {
264 let h1 = chars.next();
265 let h2 = chars.next();
266 if let (Some(a), Some(b)) = (h1, h2) {
267 if let Ok(byte) = u8::from_str_radix(&format!("{a}{b}"), 16) {
268 bytes.push(byte);
269 } else {
270 bytes.push(b'%');
272 push_char_utf8(&mut bytes, a);
273 push_char_utf8(&mut bytes, b);
274 }
275 } else {
276 bytes.push(b'%');
277 }
278 }
279 _ => push_char_utf8(&mut bytes, c),
280 }
281 }
282
283 String::from_utf8_lossy(&bytes).into_owned()
284}
285
286fn push_char_utf8(bytes: &mut Vec<u8>, c: char) {
288 let mut buf = [0u8; 4];
289 bytes.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use axum::http::{Method, Request, StatusCode};
296
297 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
298 let uri = match query {
299 Some(q) => format!("/?{q}"),
300 None => "/".to_string(),
301 };
302 Request::builder()
303 .method(Method::POST)
304 .uri(&uri)
305 .header("content-type", "application/json")
306 .body(Body::from(body.to_string()))
307 .unwrap()
308 }
309
310 fn make_form_request(body: &str, query: Option<&str>) -> Request<Body> {
311 let uri = match query {
312 Some(q) => format!("/?{q}"),
313 None => "/".to_string(),
314 };
315 Request::builder()
316 .method(Method::POST)
317 .uri(&uri)
318 .header("content-type", "application/x-www-form-urlencoded")
319 .body(Body::from(body.to_string()))
320 .unwrap()
321 }
322
323 #[test]
328 fn test_parse_query_empty() {
329 let m = parse_query("");
330 assert!(m.is_empty());
331 }
332
333 #[test]
334 fn test_parse_query_single_pair() {
335 let m = parse_query("key=value");
336 assert_eq!(m.get("key"), Some(&"value".to_string()));
337 assert_eq!(m.len(), 1);
338 }
339
340 #[test]
341 fn test_parse_query_multiple_pairs() {
342 let m = parse_query("a=1&b=2&c=3");
343 assert_eq!(m.get("a"), Some(&"1".to_string()));
344 assert_eq!(m.get("b"), Some(&"2".to_string()));
345 assert_eq!(m.get("c"), Some(&"3".to_string()));
346 }
347
348 #[test]
349 fn test_parse_query_no_value() {
350 let m = parse_query("key");
351 assert_eq!(m.get("key"), Some(&"".to_string()));
352 }
353
354 #[test]
355 fn test_parse_query_url_encoded() {
356 let m = parse_query("name=hello%20world&email=a%40b.com");
357 assert_eq!(m.get("name"), Some(&"hello world".to_string()));
358 assert_eq!(m.get("email"), Some(&"a@b.com".to_string()));
359 }
360
361 #[test]
362 fn test_parse_query_plus_for_space() {
363 let m = parse_query("q=hello+world");
364 assert_eq!(m.get("q"), Some(&"hello world".to_string()));
365 }
366
367 #[test]
368 fn test_parse_query_skip_empty_pairs() {
369 let m = parse_query("a=1&&b=2&");
370 assert_eq!(m.len(), 2);
371 assert_eq!(m.get("a"), Some(&"1".to_string()));
372 assert_eq!(m.get("b"), Some(&"2".to_string()));
373 }
374
375 #[test]
376 fn test_url_decode_basic() {
377 assert_eq!(url_decode("hello"), "hello");
378 assert_eq!(url_decode("hello%20world"), "hello world");
379 assert_eq!(url_decode("a%40b"), "a@b");
380 assert_eq!(url_decode("a+b"), "a b");
381 }
382
383 #[test]
384 fn test_url_decode_utf8_multibyte() {
385 assert_eq!(url_decode("%E9%B2%9C%E8%A7%86%E8%BE%BE"), "鲜视达");
387 assert_eq!(url_decode("%E5%B7%A5%E5%85%B7%E7%AE%B1"), "工具箱");
388 assert_eq!(
390 url_decode("q=%E9%B2%9C%E8%A7%86%E8%BE%BE+plus"),
391 "q=鲜视达 plus"
392 );
393 }
394
395 #[test]
396 fn test_url_decode_invalid_utf8_lossy() {
397 let decoded = url_decode("%FF%FE");
399 assert!(decoded.contains('\u{FFFD}'));
400 }
401
402 #[test]
403 fn test_url_decode_trailing_percent() {
404 assert_eq!(url_decode("100%"), "100%");
406 }
407
408 #[tokio::test]
413 async fn test_fetch_post_data_json_body_only() {
414 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
415 let data = fetch_post_data(req).await.unwrap();
416 assert_eq!(data["name"], "alice");
417 assert_eq!(data["age"], 30);
418 }
419
420 #[tokio::test]
421 async fn test_fetch_post_data_query_only() {
422 let req = make_json_request("", Some("page=1&size=10"));
423 let data = fetch_post_data(req).await.unwrap();
424 assert_eq!(data["page"], "1");
425 assert_eq!(data["size"], "10");
426 }
427
428 #[tokio::test]
429 async fn test_fetch_post_data_body_overrides_query() {
430 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
432 let data = fetch_post_data(req).await.unwrap();
433 assert_eq!(data["page"], 99); assert_eq!(data["size"], "10"); }
436
437 #[tokio::test]
438 async fn test_fetch_post_data_form_urlencoded() {
439 let req = make_form_request("name=bob&age=25", None);
440 let data = fetch_post_data(req).await.unwrap();
441 assert_eq!(data["name"], "bob");
442 assert_eq!(data["age"], "25");
443 }
444
445 #[tokio::test]
446 async fn test_fetch_post_data_empty_body() {
447 let req = make_json_request("", None);
448 let data = fetch_post_data(req).await.unwrap();
449 assert!(data.as_object().unwrap().is_empty());
450 }
451
452 #[tokio::test]
453 async fn test_fetch_post_data_invalid_json() {
454 let req = make_json_request("{invalid}", None);
455 let result = fetch_post_data(req).await;
456 assert!(result.is_err());
457 }
458
459 #[tokio::test]
460 async fn test_fetch_post_data_by_key() {
461 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
462 let name = fetch_post_data_by_key(req, "name").await.unwrap();
463 assert_eq!(name, Some(Value::String("alice".to_string())));
464 }
465
466 #[tokio::test]
467 async fn test_fetch_post_data_by_key_missing() {
468 let req = make_json_request(r#"{"name":"alice"}"#, None);
469 let age = fetch_post_data_by_key(req, "age").await.unwrap();
470 assert_eq!(age, None);
471 }
472
473 #[tokio::test]
474 async fn test_fetch_post_data_array_value_in_body() {
475 let req = make_json_request(r#"{"ids":[1,2,3]}"#, None);
476 let data = fetch_post_data(req).await.unwrap();
477 assert_eq!(data["ids"], serde_json::json!([1, 2, 3]));
478 }
479
480 #[tokio::test]
481 async fn test_fetch_post_data_nested_object_in_body() {
482 let req = make_json_request(r#"{"user":{"name":"alice","age":30}}"#, None);
483 let data = fetch_post_data(req).await.unwrap();
484 assert_eq!(data["user"]["name"], "alice");
485 assert_eq!(data["user"]["age"], 30);
486 }
487
488 #[tokio::test]
493 async fn test_fetch_body_data_json() {
494 let req = make_json_request(r#"{"name":"alice"}"#, Some("ignored=1"));
495 let data = fetch_body_data(req).await.unwrap();
496 assert_eq!(data["name"], "alice");
497 assert!(data.get("ignored").is_none());
499 }
500
501 #[tokio::test]
502 async fn test_fetch_body_data_empty() {
503 let req = make_json_request("", None);
504 let data = fetch_body_data(req).await.unwrap();
505 assert!(data.as_object().unwrap().is_empty());
506 }
507
508 #[test]
513 fn test_fetch_query_data_basic() {
514 let req = Request::builder()
515 .method(Method::GET)
516 .uri("/?page=1&size=10")
517 .body(Body::empty())
518 .unwrap();
519 let data = fetch_query_data(&req);
520 assert_eq!(data["page"], "1");
521 assert_eq!(data["size"], "10");
522 }
523
524 #[test]
525 fn test_fetch_query_data_no_query() {
526 let req = Request::builder()
527 .method(Method::GET)
528 .uri("/")
529 .body(Body::empty())
530 .unwrap();
531 let data = fetch_query_data(&req);
532 assert!(data.as_object().unwrap().is_empty());
533 }
534
535 #[test]
536 fn test_fetch_query_data_by_key_found() {
537 let req = Request::builder()
538 .method(Method::GET)
539 .uri("/?page=1&size=10")
540 .body(Body::empty())
541 .unwrap();
542 assert_eq!(
543 fetch_query_data_by_key(&req, "page"),
544 Some(Value::String("1".to_string()))
545 );
546 assert_eq!(
547 fetch_query_data_by_key(&req, "size"),
548 Some(Value::String("10".to_string()))
549 );
550 }
551
552 #[test]
553 fn test_fetch_query_data_by_key_not_found() {
554 let req = Request::builder()
555 .method(Method::GET)
556 .uri("/?page=1")
557 .body(Body::empty())
558 .unwrap();
559 assert_eq!(fetch_query_data_by_key(&req, "missing"), None);
560 }
561
562 #[tokio::test]
567 async fn test_post_data_via_axum_handler() {
568 use axum::routing::post;
569 use tower::ServiceExt;
570
571 async fn handler(req: Request<Body>) -> (StatusCode, String) {
572 let data = fetch_post_data(req).await.unwrap();
573 let name = data["name"].as_str().unwrap_or("unknown");
574 let age = data["age"].as_i64().unwrap_or(0);
575 (StatusCode::OK, format!("{name} is {age}"))
576 }
577
578 let router = axum::Router::new().route("/", post(handler));
579
580 let req = Request::builder()
581 .method(Method::POST)
582 .uri("/")
583 .header("content-type", "application/json")
584 .body(Body::from(r#"{"name":"alice","age":30}"#))
585 .unwrap();
586 let resp = router.oneshot(req).await.unwrap();
587 assert_eq!(resp.status(), StatusCode::OK);
588
589 use http_body_util::BodyExt;
590 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
591 assert_eq!(&bytes[..], b"alice is 30");
592 }
593
594 #[tokio::test]
607 async fn test_php_consistency_post_data_merges_body_and_query() {
608 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
612 let data = fetch_post_data(req).await.unwrap();
613 assert_eq!(data["page"], 99, "body 应覆盖 query 同名字段");
614 assert_eq!(data["size"], "10", "query 字段应保留");
615 }
616
617 #[tokio::test]
618 async fn test_php_consistency_post_data_form_urlencoded_body() {
619 let req = make_form_request("name=bob&age=25", None);
623 let data = fetch_post_data(req).await.unwrap();
624 assert_eq!(data["name"], "bob");
625 assert_eq!(data["age"], "25");
626 }
627
628 #[tokio::test]
629 async fn test_php_consistency_post_data_by_key_returns_value() {
630 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
634 let name = fetch_post_data_by_key(req, "name").await.unwrap();
635 assert_eq!(name, Some(Value::String("alice".to_string())));
636 }
637
638 #[test]
639 fn test_php_consistency_get_data_returns_only_query() {
640 let req = Request::builder()
644 .method(Method::GET)
645 .uri("/?page=1&size=10")
646 .body(Body::empty())
647 .unwrap();
648 let data = fetch_query_data(&req);
649 assert_eq!(data["page"], "1");
650 assert_eq!(data["size"], "10");
651 assert!(data.get("body").is_none());
653 }
654
655 #[test]
656 fn test_php_consistency_get_data_by_key_returns_query_value() {
657 let req = Request::builder()
661 .method(Method::GET)
662 .uri("/?page=1&size=10")
663 .body(Body::empty())
664 .unwrap();
665 assert_eq!(
666 fetch_query_data_by_key(&req, "page"),
667 Some(Value::String("1".to_string()))
668 );
669 assert_eq!(fetch_query_data_by_key(&req, "missing"), None);
670 }
671}