postgrest_parser/parser/
rpc.rs1use crate::ast::RpcParams;
2use crate::error::{Error, ParseError};
3use crate::parser::{parse_json_body, parse_order, parse_select};
4use serde_json::Value;
5use std::collections::HashMap;
6
7pub fn parse_rpc_params(
25 function_name: &str,
26 query_string: &str,
27 body: Option<&str>,
28) -> Result<RpcParams, Error> {
29 let args = if let Some(body_str) = body {
31 let json_value = parse_json_body(body_str)?;
32 validate_rpc_args(json_value)?
33 } else {
34 HashMap::new()
35 };
36
37 let mut params = RpcParams::new(function_name, args);
38
39 let query_params = parse_query_params(query_string);
41
42 let filter_pairs: Vec<(String, String)> = query_params
44 .iter()
45 .filter(|(k, _)| !is_reserved_key(k))
46 .map(|(k, v)| (k.clone(), v.clone()))
47 .collect();
48
49 if !filter_pairs.is_empty() {
50 let parsed_params = crate::parse_params_from_pairs(filter_pairs)?;
51 params = params.with_filters(parsed_params.filters);
52 }
53
54 if let Some(order_str) = query_params.get("order") {
56 let order = parse_order(order_str)?;
57 params = params.with_order(order);
58 }
59
60 if let Some(limit_str) = query_params.get("limit") {
62 let limit = limit_str
63 .parse::<u64>()
64 .map_err(|_| Error::Parse(ParseError::InvalidLimit(limit_str.to_string())))?;
65 params = params.with_limit(limit);
66 }
67
68 if let Some(offset_str) = query_params.get("offset") {
70 let offset = offset_str
71 .parse::<u64>()
72 .map_err(|_| Error::Parse(ParseError::InvalidOffset(offset_str.to_string())))?;
73 params = params.with_offset(offset);
74 }
75
76 if let Some(select_str) = query_params.get("select") {
78 let returning = parse_select(select_str)?;
79 params = params.with_returning(returning);
80 }
81
82 Ok(params)
83}
84
85fn validate_rpc_args(value: Value) -> Result<HashMap<String, Value>, Error> {
86 match value {
87 Value::Object(map) => {
88 let mut hash_map = HashMap::new();
89 for (k, v) in map {
90 hash_map.insert(k, v);
91 }
92 Ok(hash_map)
93 }
94 _ => Err(Error::Parse(ParseError::InvalidJsonBody(
95 "RPC arguments must be a JSON object".to_string(),
96 ))),
97 }
98}
99
100fn parse_query_params(query_string: &str) -> HashMap<String, String> {
101 if query_string.is_empty() {
102 return HashMap::new();
103 }
104
105 query_string
106 .split('&')
107 .filter_map(|pair| {
108 let parts: Vec<&str> = pair.splitn(2, '=').collect();
109 if parts.len() == 2 {
110 Some((parts[0].to_string(), parts[1].to_string()))
111 } else {
112 None
113 }
114 })
115 .collect()
116}
117
118fn is_reserved_key(key: &str) -> bool {
119 matches!(key, "select" | "order" | "limit" | "offset")
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_parse_rpc_params_with_body() {
128 let body = r#"{"user_id": 123, "status": "active"}"#;
129 let params = parse_rpc_params("get_user_posts", "", Some(body)).unwrap();
130
131 assert_eq!(params.function_name, "get_user_posts");
132 assert_eq!(params.args.len(), 2);
133 assert_eq!(params.args.get("user_id"), Some(&Value::Number(123.into())));
134 }
135
136 #[test]
137 fn test_parse_rpc_params_no_body() {
138 let params = parse_rpc_params("health_check", "", None).unwrap();
139
140 assert_eq!(params.function_name, "health_check");
141 assert!(params.args.is_empty());
142 }
143
144 #[test]
145 fn test_parse_rpc_params_with_limit_offset() {
146 let params = parse_rpc_params("list_users", "limit=10&offset=20", None).unwrap();
147
148 assert_eq!(params.limit, Some(10));
149 assert_eq!(params.offset, Some(20));
150 }
151
152 #[test]
153 fn test_parse_rpc_params_with_order() {
154 let params = parse_rpc_params("list_users", "order=created_at.desc", None).unwrap();
155
156 assert_eq!(params.order.len(), 1);
157 }
158
159 #[test]
160 fn test_parse_rpc_params_with_filters() {
161 let params = parse_rpc_params("list_users", "age=gt.18&status=eq.active", None).unwrap();
162
163 assert_eq!(params.filters.len(), 2);
164 }
165
166 #[test]
167 fn test_parse_rpc_params_with_select() {
168 let params = parse_rpc_params("get_posts", "select=id,title,author", None).unwrap();
169
170 assert!(params.returning.is_some());
171 assert_eq!(params.returning.unwrap().len(), 3);
172 }
173
174 #[test]
175 fn test_parse_rpc_invalid_body() {
176 let body = r#"["not", "an", "object"]"#;
177 let result = parse_rpc_params("test_func", "", Some(body));
178
179 assert!(result.is_err());
180 }
181
182 #[test]
183 fn test_parse_rpc_complex_scenario() {
184 let body = r#"{"department_id": 5}"#;
185 let query = "age=gte.25&salary=lt.100000&order=name.asc&limit=50&select=id,name,salary";
186 let params = parse_rpc_params("find_employees", query, Some(body)).unwrap();
187
188 assert_eq!(params.function_name, "find_employees");
189 assert_eq!(params.args.len(), 1);
190 assert_eq!(params.filters.len(), 2);
191 assert_eq!(params.order.len(), 1);
192 assert_eq!(params.limit, Some(50));
193 assert!(params.returning.is_some());
194 }
195}