Skip to main content

postgrest_parser/parser/
logic.rs

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}