postrust_core/api_request/
payload.rs1use super::types::*;
6use crate::error::{Error, Result};
7use bytes::Bytes;
8use std::collections::HashSet;
9
10pub fn parse_payload(body: Bytes, content_type: &MediaType) -> Result<Option<Payload>> {
12 if body.is_empty() {
13 return Ok(None);
14 }
15
16 match content_type {
17 MediaType::ApplicationJson => parse_json_payload(body),
18 MediaType::UrlEncoded => parse_urlencoded_payload(body),
19 MediaType::TextCsv => {
20 Ok(Some(Payload::RawJson(body)))
22 }
23 MediaType::OctetStream | MediaType::TextPlain | MediaType::TextXml => {
24 Ok(Some(Payload::RawPayload(body)))
25 }
26 _ => parse_json_payload(body),
27 }
28}
29
30fn parse_json_payload(body: Bytes) -> Result<Option<Payload>> {
32 let value: serde_json::Value =
34 serde_json::from_slice(&body).map_err(|e| Error::InvalidBody(e.to_string()))?;
35
36 let keys = extract_json_keys(&value);
37
38 Ok(Some(Payload::ProcessedJson { raw: body, keys }))
39}
40
41fn extract_json_keys(value: &serde_json::Value) -> HashSet<String> {
43 match value {
44 serde_json::Value::Object(map) => map.keys().cloned().collect(),
45 serde_json::Value::Array(arr) => {
46 arr.iter()
48 .filter_map(|v| v.as_object())
49 .flat_map(|map| map.keys().cloned())
50 .collect()
51 }
52 _ => HashSet::new(),
53 }
54}
55
56fn parse_urlencoded_payload(body: Bytes) -> Result<Option<Payload>> {
58 let body_str =
59 std::str::from_utf8(&body).map_err(|_| Error::InvalidBody("Invalid UTF-8".into()))?;
60
61 let data: Vec<(String, String)> = url::form_urlencoded::parse(body_str.as_bytes())
62 .map(|(k, v)| (k.to_string(), v.to_string()))
63 .collect();
64
65 let keys: HashSet<String> = data.iter().map(|(k, _)| k.clone()).collect();
66
67 Ok(Some(Payload::ProcessedUrlEncoded { data, keys }))
68}
69
70pub fn validate_payload_columns(
72 payload: &Payload,
73 expected: &HashSet<String>,
74) -> Result<()> {
75 let keys = match payload {
76 Payload::ProcessedJson { keys, .. } => keys,
77 Payload::ProcessedUrlEncoded { keys, .. } => keys,
78 _ => return Ok(()),
79 };
80
81 for key in keys {
82 if !expected.contains(key) {
83 return Err(Error::UnknownColumn(key.clone()));
84 }
85 }
86
87 Ok(())
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_parse_json_object() {
96 let body = Bytes::from(r#"{"name": "John", "age": 30}"#);
97 let payload = parse_payload(body, &MediaType::ApplicationJson)
98 .unwrap()
99 .unwrap();
100
101 match payload {
102 Payload::ProcessedJson { keys, .. } => {
103 assert!(keys.contains("name"));
104 assert!(keys.contains("age"));
105 }
106 _ => panic!("Expected ProcessedJson"),
107 }
108 }
109
110 #[test]
111 fn test_parse_json_array() {
112 let body = Bytes::from(r#"[{"id": 1}, {"id": 2, "name": "test"}]"#);
113 let payload = parse_payload(body, &MediaType::ApplicationJson)
114 .unwrap()
115 .unwrap();
116
117 match payload {
118 Payload::ProcessedJson { keys, .. } => {
119 assert!(keys.contains("id"));
120 assert!(keys.contains("name"));
121 }
122 _ => panic!("Expected ProcessedJson"),
123 }
124 }
125
126 #[test]
127 fn test_parse_urlencoded() {
128 let body = Bytes::from("name=John&age=30");
129 let payload = parse_payload(body, &MediaType::UrlEncoded)
130 .unwrap()
131 .unwrap();
132
133 match payload {
134 Payload::ProcessedUrlEncoded { data, keys } => {
135 assert_eq!(data.len(), 2);
136 assert!(keys.contains("name"));
137 assert!(keys.contains("age"));
138 }
139 _ => panic!("Expected ProcessedUrlEncoded"),
140 }
141 }
142
143 #[test]
144 fn test_parse_empty_body() {
145 let body = Bytes::new();
146 let payload = parse_payload(body, &MediaType::ApplicationJson).unwrap();
147 assert!(payload.is_none());
148 }
149
150 #[test]
151 fn test_parse_octet_stream() {
152 let body = Bytes::from(vec![0u8, 1, 2, 3]);
153 let payload = parse_payload(body.clone(), &MediaType::OctetStream)
154 .unwrap()
155 .unwrap();
156
157 match payload {
158 Payload::RawPayload(data) => {
159 assert_eq!(data, body);
160 }
161 _ => panic!("Expected RawPayload"),
162 }
163 }
164}