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(payload: &Payload, expected: &HashSet<String>) -> Result<()> {
72 let keys = match payload {
73 Payload::ProcessedJson { keys, .. } => keys,
74 Payload::ProcessedUrlEncoded { keys, .. } => keys,
75 _ => return Ok(()),
76 };
77
78 for key in keys {
79 if !expected.contains(key) {
80 return Err(Error::UnknownColumn(key.clone()));
81 }
82 }
83
84 Ok(())
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_parse_json_object() {
93 let body = Bytes::from(r#"{"name": "John", "age": 30}"#);
94 let payload = parse_payload(body, &MediaType::ApplicationJson)
95 .unwrap()
96 .unwrap();
97
98 match payload {
99 Payload::ProcessedJson { keys, .. } => {
100 assert!(keys.contains("name"));
101 assert!(keys.contains("age"));
102 }
103 _ => panic!("Expected ProcessedJson"),
104 }
105 }
106
107 #[test]
108 fn test_parse_json_array() {
109 let body = Bytes::from(r#"[{"id": 1}, {"id": 2, "name": "test"}]"#);
110 let payload = parse_payload(body, &MediaType::ApplicationJson)
111 .unwrap()
112 .unwrap();
113
114 match payload {
115 Payload::ProcessedJson { keys, .. } => {
116 assert!(keys.contains("id"));
117 assert!(keys.contains("name"));
118 }
119 _ => panic!("Expected ProcessedJson"),
120 }
121 }
122
123 #[test]
124 fn test_parse_urlencoded() {
125 let body = Bytes::from("name=John&age=30");
126 let payload = parse_payload(body, &MediaType::UrlEncoded)
127 .unwrap()
128 .unwrap();
129
130 match payload {
131 Payload::ProcessedUrlEncoded { data, keys } => {
132 assert_eq!(data.len(), 2);
133 assert!(keys.contains("name"));
134 assert!(keys.contains("age"));
135 }
136 _ => panic!("Expected ProcessedUrlEncoded"),
137 }
138 }
139
140 #[test]
141 fn test_parse_empty_body() {
142 let body = Bytes::new();
143 let payload = parse_payload(body, &MediaType::ApplicationJson).unwrap();
144 assert!(payload.is_none());
145 }
146
147 #[test]
148 fn test_parse_octet_stream() {
149 let body = Bytes::from(vec![0u8, 1, 2, 3]);
150 let payload = parse_payload(body.clone(), &MediaType::OctetStream)
151 .unwrap()
152 .unwrap();
153
154 match payload {
155 Payload::RawPayload(data) => {
156 assert_eq!(data, body);
157 }
158 _ => panic!("Expected RawPayload"),
159 }
160 }
161}