1use super::{common::parse_field_fallback, filter::parse_filter};
2use crate::ast::{
3 Field, Filter, FilterOperator, FilterValue, LogicCondition, LogicOperator, LogicTree,
4};
5use crate::error::ParseError;
6
7pub fn parse_logic(key: &str, value: &str) -> Result<LogicTree, ParseError> {
8 let (negated, operator) = parse_logic_key(key)?;
9
10 let conditions_str = extract_conditions_str(value)?;
11 let conditions = parse_conditions(&conditions_str)?;
12
13 Ok(LogicTree {
14 operator,
15 conditions,
16 negated,
17 })
18}
19
20pub fn logic_key(key: &str) -> bool {
21 let key_lower = key.to_lowercase();
22 matches!(key_lower.as_str(), "and" | "or" | "not.and" | "not.or")
23}
24
25fn parse_logic_key(key: &str) -> Result<(bool, LogicOperator), ParseError> {
26 let key_lower = key.to_lowercase();
27
28 if let Some(rest) = key_lower.strip_prefix("not.") {
29 match rest {
30 "and" => Ok((true, LogicOperator::And)),
31 "or" => Ok((true, LogicOperator::Or)),
32 _ => Err(ParseError::InvalidLogicExpression(format!(
33 "invalid key: {}",
34 key
35 ))),
36 }
37 } else {
38 match key_lower.as_str() {
39 "and" => Ok((false, LogicOperator::And)),
40 "or" => Ok((false, LogicOperator::Or)),
41 _ => Err(ParseError::InvalidLogicExpression(format!(
42 "invalid key: {}",
43 key
44 ))),
45 }
46 }
47}
48
49fn extract_conditions_str(value: &str) -> Result<String, ParseError> {
50 let trimmed = value.trim();
51
52 if trimmed.starts_with('(') && trimmed.ends_with(')') {
53 Ok(trimmed[1..trimmed.len() - 1].to_string())
54 } else {
55 Err(ParseError::LogicExpressionNotWrapped)
56 }
57}
58
59fn parse_conditions(str: &str) -> Result<Vec<LogicCondition>, ParseError> {
60 let parts = split_at_top_level_commas(str)?;
61
62 parts
63 .iter()
64 .map(|part| parse_condition(part.trim()))
65 .collect()
66}
67
68fn split_at_top_level_commas(str: &str) -> Result<Vec<String>, ParseError> {
69 let mut parts = Vec::new();
70 let mut current = String::new();
71 let mut depth = 0;
72
73 for c in str.chars() {
74 match c {
75 '(' => {
76 depth += 1;
77 current.push(c);
78 }
79 ')' => {
80 depth -= 1;
81 if depth < 0 {
82 return Err(ParseError::UnexpectedClosingParenthesis);
83 }
84 current.push(c);
85 }
86 ',' if depth == 0 => {
87 parts.push(current.trim().to_string());
88 current.clear();
89 }
90 _ => {
91 current.push(c);
92 }
93 }
94 }
95
96 if !current.trim().is_empty() {
97 parts.push(current.trim().to_string());
98 }
99
100 if depth > 0 {
101 return Err(ParseError::UnclosedParenthesis);
102 }
103
104 Ok(parts)
105}
106
107fn parse_condition(condition_str: &str) -> Result<LogicCondition, ParseError> {
108 let trimmed = condition_str.trim();
109
110 if trimmed.is_empty() {
111 return Err(ParseError::InvalidLogicExpression(
112 "empty condition".to_string(),
113 ));
114 }
115
116 if trimmed.starts_with("and(")
117 || trimmed.starts_with("or(")
118 || trimmed.starts_with("not.and(")
119 || trimmed.starts_with("not.or(")
120 {
121 parse_nested_logic(trimmed)
122 } else {
123 parse_filter_condition(trimmed)
124 }
125}
126
127fn parse_nested_logic(str: &str) -> Result<LogicCondition, ParseError> {
128 let (negated, rest) = if let Some(stripped) = str.strip_prefix("not.") {
129 (true, stripped)
130 } else {
131 (false, str)
132 };
133
134 let (operator, inner) = if let Some(rest) = rest.strip_prefix("and(") {
135 if !rest.ends_with(')') {
136 return Err(ParseError::InvalidLogicExpression(format!(
137 "invalid nested logic: {}",
138 str
139 )));
140 }
141 (LogicOperator::And, &rest[..rest.len() - 1])
142 } else if let Some(rest) = rest.strip_prefix("or(") {
143 if !rest.ends_with(')') {
144 return Err(ParseError::InvalidLogicExpression(format!(
145 "invalid nested logic: {}",
146 str
147 )));
148 }
149 (LogicOperator::Or, &rest[..rest.len() - 1])
150 } else {
151 return Err(ParseError::InvalidLogicExpression(format!(
152 "invalid nested logic: {}",
153 str
154 )));
155 };
156
157 let conditions = parse_conditions(inner)?;
158
159 Ok(LogicCondition::Logic(LogicTree {
160 operator,
161 conditions,
162 negated,
163 }))
164}
165
166fn parse_filter_condition(str: &str) -> Result<LogicCondition, ParseError> {
167 if str.contains('=') {
168 parse_equals_notation(str)
169 } else {
170 parse_dot_notation(str)
171 }
172}
173
174fn parse_equals_notation(str: &str) -> Result<LogicCondition, ParseError> {
175 let parts: Vec<&str> = str.splitn(2, '=').collect();
176
177 if parts.len() == 2 {
178 let field_str = parts[0].trim();
179 let operator_value = parts[1].trim();
180
181 let filter = parse_filter(field_str, operator_value)?;
182 Ok(LogicCondition::Filter(filter))
183 } else {
184 Err(ParseError::InvalidFilterFormat(format!(
185 "invalid equals notation: {}",
186 str
187 )))
188 }
189}
190
191fn parse_dot_notation(str: &str) -> Result<LogicCondition, ParseError> {
192 let parts: Vec<&str> = str.split('.').collect();
193
194 if parts.len() == 3 {
195 let field_str = parts[0];
196 let operator_str = parts[1];
197 let value_str = parts[2];
198
199 let operator = parse_filter_operator(operator_str)?;
200 let value = FilterValue::Single(value_str.to_string());
201
202 let field = parse_filter_field(field_str)?;
203
204 Ok(LogicCondition::Filter(Filter {
205 field,
206 operator,
207 value,
208 quantifier: None,
209 language: None,
210 negated: false,
211 }))
212 } else if parts.len() == 4 && parts[1] == "not" {
213 let field_str = parts[0];
214 let operator_str = parts[2];
215 let value_str = parts[3];
216
217 let operator = parse_filter_operator(operator_str)?;
218 let value = FilterValue::Single(value_str.to_string());
219
220 let field = parse_filter_field(field_str)?;
221
222 Ok(LogicCondition::Filter(Filter {
223 field,
224 operator,
225 value,
226 quantifier: None,
227 language: None,
228 negated: true,
229 }))
230 } else {
231 Err(ParseError::InvalidFilterFormat(format!(
232 "invalid dot notation: {}",
233 str
234 )))
235 }
236}
237
238fn parse_filter_operator(op_str: &str) -> Result<FilterOperator, ParseError> {
239 match op_str.to_lowercase().as_str() {
240 "eq" => Ok(FilterOperator::Eq),
241 "neq" => Ok(FilterOperator::Neq),
242 "gt" => Ok(FilterOperator::Gt),
243 "gte" => Ok(FilterOperator::Gte),
244 "lt" => Ok(FilterOperator::Lt),
245 "lte" => Ok(FilterOperator::Lte),
246 "like" => Ok(FilterOperator::Like),
247 "ilike" => Ok(FilterOperator::Ilike),
248 "match" => Ok(FilterOperator::Match),
249 "imatch" => Ok(FilterOperator::Imatch),
250 "in" => Ok(FilterOperator::In),
251 "is" => Ok(FilterOperator::Is),
252 "fts" => Ok(FilterOperator::Fts),
253 "plfts" => Ok(FilterOperator::Plfts),
254 "phfts" => Ok(FilterOperator::Phfts),
255 "wfts" => Ok(FilterOperator::Wfts),
256 "cs" => Ok(FilterOperator::Cs),
257 "cd" => Ok(FilterOperator::Cd),
258 "ov" => Ok(FilterOperator::Ov),
259 "sl" => Ok(FilterOperator::Sl),
260 "sr" => Ok(FilterOperator::Sr),
261 "nxl" => Ok(FilterOperator::Nxl),
262 "nxr" => Ok(FilterOperator::Nxr),
263 "adj" => Ok(FilterOperator::Adj),
264 _ => Err(ParseError::UnknownOperator(op_str.to_string())),
265 }
266}
267
268fn parse_filter_field(field_str: &str) -> Result<Field, ParseError> {
269 match crate::parser::common::field(field_str) {
270 Ok((_, field)) => Ok(field),
271 Err(_) => parse_field_fallback(field_str),
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_parse_logic_and() {
281 let result = parse_logic("and", "(id.eq.1,name.eq.john)");
282 assert!(result.is_ok());
283 let tree = result.unwrap();
284 assert_eq!(tree.operator, LogicOperator::And);
285 assert!(!tree.negated);
286 assert_eq!(tree.conditions.len(), 2);
287 }
288
289 #[test]
290 fn test_parse_logic_or() {
291 let result = parse_logic("or", "(id.eq.1,id.eq.2)");
292 assert!(result.is_ok());
293 let tree = result.unwrap();
294 assert_eq!(tree.operator, LogicOperator::Or);
295 }
296
297 #[test]
298 fn test_parse_logic_negated() {
299 let result = parse_logic("not.and", "(id.eq.1,name.eq.john)");
300 assert!(result.is_ok());
301 let tree = result.unwrap();
302 assert!(tree.negated);
303 }
304
305 #[test]
306 fn test_parse_logic_nested() {
307 let result = parse_logic("and", "(id.eq.1,or(id.eq.2,id.eq.3))");
308 assert!(result.is_ok());
309 let tree = result.unwrap();
310 assert_eq!(tree.conditions.len(), 2);
311
312 assert!(matches!(&tree.conditions[1], LogicCondition::Logic(_)));
313 }
314
315 #[test]
316 fn test_logic_key() {
317 assert!(logic_key("and"));
318 assert!(logic_key("or"));
319 assert!(logic_key("not.and"));
320 assert!(logic_key("not.or"));
321 assert!(!logic_key("id"));
322 }
323
324 #[test]
325 fn test_parse_condition_filter() {
326 let result = parse_condition("id.eq.1");
327 assert!(result.is_ok());
328 assert!(matches!(result.unwrap(), LogicCondition::Filter(_)));
329 }
330
331 #[test]
332 fn test_parse_condition_nested() {
333 let result = parse_condition("and(id.eq.1,name.eq.john)");
334 assert!(result.is_ok());
335 assert!(matches!(result.unwrap(), LogicCondition::Logic(_)));
336 }
337
338 #[test]
339 fn test_parse_condition_equals_notation() {
340 let result = parse_condition("id=eq.1");
341 assert!(result.is_ok());
342 assert!(matches!(result.unwrap(), LogicCondition::Filter(_)));
343 }
344
345 #[test]
346 fn test_parse_condition_invalid() {
347 let result = parse_condition("invalid");
348 assert!(matches!(result, Err(ParseError::InvalidFilterFormat(_))));
349 }
350
351 #[test]
352 fn test_split_at_top_level_commas() {
353 let result = split_at_top_level_commas("id.eq.1,name.eq.john,or(x.eq.1,y.eq.2)");
354 assert!(result.is_ok());
355 let parts = result.unwrap();
356 assert_eq!(parts.len(), 3);
357 }
358
359 #[test]
360 fn test_parse_nested_logic() {
361 let result = parse_nested_logic("and(id.eq.1,name.eq.john)");
362 assert!(result.is_ok());
363 let condition = result.unwrap();
364 assert!(matches!(condition, LogicCondition::Logic(_)));
365 }
366
367 #[test]
368 fn test_parse_filter_condition_equals() {
369 let result = parse_equals_notation("id=eq.1");
370 assert!(result.is_ok());
371 assert!(matches!(result.unwrap(), LogicCondition::Filter(_)));
372 }
373
374 #[test]
375 fn test_parse_filter_condition_dot() {
376 let result = parse_dot_notation("id.eq.1");
377 assert!(result.is_ok());
378 assert!(matches!(result.unwrap(), LogicCondition::Filter(_)));
379 }
380}