structured_proxy/transcode/
body.rs1use axum::http::HeaderMap;
8use serde_json::Value;
9
10pub fn parse_body(content_type: Option<&str>, body: &[u8]) -> Result<Value, BodyError> {
16 if body.is_empty() {
17 return Ok(Value::Object(serde_json::Map::new()));
18 }
19
20 match content_type {
21 Some(ct) if ct.starts_with("application/x-www-form-urlencoded") => {
22 let pairs: Vec<(String, String)> = serde_urlencoded::from_bytes(body)
23 .map_err(|e| BodyError::FormDecode(e.to_string()))?;
24 let mut map = serde_json::Map::new();
25 for (key, value) in pairs {
26 map.insert(key, Value::String(value));
27 }
28 Ok(Value::Object(map))
29 }
30 _ => serde_json::from_slice(body).map_err(|e| BodyError::JsonDecode(e.to_string())),
31 }
32}
33
34pub fn content_type(headers: &HeaderMap) -> Option<&str> {
36 headers
37 .get("content-type")
38 .and_then(|v| v.to_str().ok())
39 .map(|ct| ct.split(';').next().unwrap_or(ct).trim())
40}
41
42#[derive(Debug, thiserror::Error)]
43pub enum BodyError {
44 #[error("invalid JSON: {0}")]
45 JsonDecode(String),
46 #[error("invalid form data: {0}")]
47 FormDecode(String),
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_empty_body() {
56 let result = parse_body(Some("application/json"), b"").unwrap();
57 assert_eq!(result, serde_json::json!({}));
58 }
59
60 #[test]
61 fn test_json_body() {
62 let body = br#"{"username":"alice","password":"secret"}"#;
63 let result = parse_body(Some("application/json"), body).unwrap();
64 assert_eq!(result["username"], "alice");
65 assert_eq!(result["password"], "secret");
66 }
67
68 #[test]
69 fn test_form_urlencoded_body() {
70 let body =
71 b"grant_type=authorization_code&code=abc123&redirect_uri=https%3A%2F%2Fexample.com";
72 let result = parse_body(Some("application/x-www-form-urlencoded"), body).unwrap();
73 assert_eq!(result["grant_type"], "authorization_code");
74 assert_eq!(result["code"], "abc123");
75 assert_eq!(result["redirect_uri"], "https://example.com");
76 }
77
78 #[test]
79 fn test_form_with_charset() {
80 let body = b"key=value";
81 let result = parse_body(
82 Some("application/x-www-form-urlencoded; charset=utf-8"),
83 body,
84 )
85 .unwrap();
86 assert_eq!(result["key"], "value");
87 }
88
89 #[test]
90 fn test_no_content_type_parses_as_json() {
91 let body = br#"{"foo":"bar"}"#;
92 let result = parse_body(None, body).unwrap();
93 assert_eq!(result["foo"], "bar");
94 }
95
96 #[test]
97 fn test_invalid_json() {
98 let body = b"not json";
99 assert!(parse_body(Some("application/json"), body).is_err());
100 }
101
102 #[test]
103 fn test_content_type_extraction() {
104 let mut headers = HeaderMap::new();
105 headers.insert(
106 "content-type",
107 "application/json; charset=utf-8".parse().unwrap(),
108 );
109 assert_eq!(content_type(&headers), Some("application/json"));
110 }
111
112 #[test]
113 fn test_content_type_missing() {
114 let headers = HeaderMap::new();
115 assert_eq!(content_type(&headers), None);
116 }
117}