postrust_core/api_request/
query_params.rs

1//! Query parameter parsing using nom.
2//!
3//! Parses URL query strings into structured filter, select, order, and range data.
4//! Mirrors PostgREST's QueryParams.hs parsing logic.
5
6use super::types::*;
7use crate::error::{Error, Result};
8use nom::{
9    branch::alt,
10    bytes::complete::{tag, take_until, take_while1},
11    character::complete::{char, digit1},
12    combinator::{map, opt, value},
13    multi::{many0, separated_list0},
14    sequence::preceded,
15    IResult,
16};
17use percent_encoding::percent_decode_str;
18
19/// Parse a query string into QueryParams.
20pub fn parse_query_params(query: &str) -> Result<QueryParams> {
21    let mut params = QueryParams::default();
22
23    if query.is_empty() {
24        return Ok(params);
25    }
26
27    // Sort parameters for canonical form
28    let mut pairs: Vec<(&str, &str)> = query
29        .split('&')
30        .filter_map(|pair| {
31            let mut parts = pair.splitn(2, '=');
32            Some((parts.next()?, parts.next().unwrap_or("")))
33        })
34        .collect();
35    pairs.sort_by_key(|(k, _)| *k);
36    params.canonical = pairs
37        .iter()
38        .map(|(k, v)| format!("{}={}", k, v))
39        .collect::<Vec<_>>()
40        .join("&");
41
42    for (key, value) in pairs {
43        let decoded_value = percent_decode_str(value)
44            .decode_utf8()
45            .map_err(|_| Error::InvalidQueryParam(key.into()))?
46            .to_string();
47
48        match key {
49            "select" => {
50                params.select = parse_select(&decoded_value)?;
51            }
52            "order" => {
53                let (path, terms) = parse_order_param(&decoded_value)?;
54                params.order.push((path, terms));
55            }
56            "limit" => {
57                let limit: i64 = decoded_value
58                    .parse()
59                    .map_err(|_| Error::InvalidQueryParam("limit".into()))?;
60                params.ranges.entry(String::new()).or_default().limit = Some(limit);
61            }
62            "offset" => {
63                let offset: i64 = decoded_value
64                    .parse()
65                    .map_err(|_| Error::InvalidQueryParam("offset".into()))?;
66                params.ranges.entry(String::new()).or_default().offset = offset;
67            }
68            "columns" => {
69                params.columns = Some(
70                    decoded_value
71                        .split(',')
72                        .map(|s| s.trim().to_string())
73                        .collect(),
74                );
75            }
76            "on_conflict" => {
77                params.on_conflict = Some(
78                    decoded_value
79                        .split(',')
80                        .map(|s| s.trim().to_string())
81                        .collect(),
82                );
83            }
84            "and" | "or" => {
85                let logic = parse_logic_param(key, &decoded_value)?;
86                params.logic.push((vec![], logic));
87            }
88            key if !key.starts_with('_') => {
89                // Filter parameter
90                let (path, filter) = parse_filter_param(key, &decoded_value)?;
91                if path.is_empty() {
92                    params.filter_fields.insert(filter.field.name.clone());
93                    params.filters_root.push(filter);
94                } else {
95                    params.filters.push((path, filter));
96                }
97            }
98            _ => {
99                // RPC parameters (anything else)
100                params.params.push((key.to_string(), decoded_value));
101            }
102        }
103    }
104
105    Ok(params)
106}
107
108// ============================================================================
109// Select Parsing
110// ============================================================================
111
112/// Parse the `select` parameter value.
113pub fn parse_select(input: &str) -> Result<Vec<SelectItem>> {
114    if input.is_empty() {
115        return Ok(vec![]);
116    }
117
118    match parse_select_items(input) {
119        Ok((_, items)) => Ok(items),
120        Err(_) => Err(Error::InvalidQueryParam("select".into())),
121    }
122}
123
124fn parse_select_items(input: &str) -> IResult<&str, Vec<SelectItem>> {
125    separated_list0(char(','), parse_select_item)(input)
126}
127
128fn parse_select_item(input: &str) -> IResult<&str, SelectItem> {
129    alt((
130        parse_spread_relation,
131        parse_relation_select,
132        parse_field_select,
133    ))(input)
134}
135
136/// Parse spread relation: `...relation`
137fn parse_spread_relation(input: &str) -> IResult<&str, SelectItem> {
138    let (input, _) = tag("...")(input)?;
139    let (input, relation) = parse_identifier(input)?;
140    let (input, hint) = opt(preceded(char('!'), parse_identifier))(input)?;
141    let (input, join_type) = opt(preceded(char('!'), parse_join_type))(input)?;
142
143    Ok((
144        input,
145        SelectItem::SpreadRelation {
146            relation: relation.to_string(),
147            hint: hint.map(|s| s.to_string()),
148            join_type,
149        },
150    ))
151}
152
153/// Parse relation with embedded select: `relation(select_items)`
154fn parse_relation_select(input: &str) -> IResult<&str, SelectItem> {
155    let (input, name) = parse_identifier(input)?;
156    let (input, alias) = opt(preceded(char(':'), parse_identifier))(input)?;
157    let (input, hint) = opt(preceded(char('!'), parse_identifier))(input)?;
158    let (input, join_type) = opt(preceded(char('!'), parse_join_type))(input)?;
159    let (input, _) = char('(')(input)?;
160    let (input, _nested) = take_until(")")(input)?;
161    let (input, _) = char(')')(input)?;
162
163    Ok((
164        input,
165        SelectItem::Relation {
166            relation: name.to_string(),
167            alias: alias.map(|s| s.to_string()),
168            hint: hint.map(|s| s.to_string()),
169            join_type,
170        },
171    ))
172}
173
174/// Parse field select: `field`, `field::cast`, `field:alias`, `agg(field)`
175fn parse_field_select(input: &str) -> IResult<&str, SelectItem> {
176    // Check for aggregate function
177    let (input, aggregate) = opt(parse_aggregate_prefix)(input)?;
178
179    let (input, name) = parse_identifier(input)?;
180    let (input, json_path) = parse_json_path(input)?;
181
182    // Close aggregate if present
183    let (input, aggregate_cast) = if aggregate.is_some() {
184        let (input, _) = char(')')(input)?;
185        let (input, cast) = opt(preceded(tag("::"), parse_identifier))(input)?;
186        (input, cast.map(|s| s.to_string()))
187    } else {
188        (input, None)
189    };
190
191    let (input, cast) = if aggregate.is_none() {
192        opt(preceded(tag("::"), parse_identifier))(input)?
193    } else {
194        (input, None)
195    };
196
197    let (input, alias) = opt(preceded(char(':'), parse_identifier))(input)?;
198
199    Ok((
200        input,
201        SelectItem::Field {
202            field: Field {
203                name: name.to_string(),
204                json_path,
205            },
206            aggregate,
207            aggregate_cast,
208            cast: cast.map(|s| s.to_string()),
209            alias: alias.map(|s| s.to_string()),
210        },
211    ))
212}
213
214fn parse_aggregate_prefix(input: &str) -> IResult<&str, AggregateFunction> {
215    alt((
216        value(AggregateFunction::Sum, tag("sum(")),
217        value(AggregateFunction::Avg, tag("avg(")),
218        value(AggregateFunction::Max, tag("max(")),
219        value(AggregateFunction::Min, tag("min(")),
220        value(AggregateFunction::Count, tag("count(")),
221    ))(input)
222}
223
224fn parse_join_type(input: &str) -> IResult<&str, JoinType> {
225    alt((
226        value(JoinType::Inner, tag("inner")),
227        value(JoinType::Left, tag("left")),
228    ))(input)
229}
230
231// ============================================================================
232// Filter Parsing
233// ============================================================================
234
235/// Parse a filter parameter (key=value where key is a field name).
236fn parse_filter_param(key: &str, value: &str) -> Result<(EmbedPath, Filter)> {
237    // Parse the key for embedded path: rel.field or field
238    let (path, field_name) = parse_filter_key(key)?;
239
240    // Parse the value for operator and operand
241    let op_expr = parse_filter_value(value)?;
242
243    let filter = Filter::new(Field::simple(field_name), op_expr);
244    Ok((path, filter))
245}
246
247/// Parse a filter key into path and field name.
248fn parse_filter_key(key: &str) -> Result<(EmbedPath, String)> {
249    let parts: Vec<&str> = key.split('.').collect();
250    if parts.is_empty() {
251        return Err(Error::InvalidQueryParam(key.into()));
252    }
253
254    if parts.len() == 1 {
255        return Ok((vec![], parts[0].to_string()));
256    }
257
258    let path: Vec<String> = parts[..parts.len() - 1].iter().map(|s| s.to_string()).collect();
259    let field = parts.last().unwrap().to_string();
260    Ok((path, field))
261}
262
263/// Parse filter value: `operator.value` or `not.operator.value`
264fn parse_filter_value(value: &str) -> Result<OpExpr> {
265    let (value, negated) = if let Some(rest) = value.strip_prefix("not.") {
266        (rest, true)
267    } else {
268        (value, false)
269    };
270
271    let operation = parse_operation(value)?;
272    Ok(OpExpr { negated, operation })
273}
274
275/// Parse an operation: `eq.value`, `in.(a,b,c)`, `is.null`, etc.
276fn parse_operation(value: &str) -> Result<Operation> {
277    // Try each operator pattern
278    if let Some(rest) = value.strip_prefix("eq.") {
279        return Ok(Operation::Quant {
280            op: QuantOperator::Equal,
281            quantifier: None,
282            value: rest.to_string(),
283        });
284    }
285    if let Some(rest) = value.strip_prefix("neq.") {
286        return Ok(Operation::Simple {
287            op: SimpleOperator::NotEqual,
288            value: rest.to_string(),
289        });
290    }
291    if let Some(rest) = value.strip_prefix("gt.") {
292        return Ok(Operation::Quant {
293            op: QuantOperator::GreaterThan,
294            quantifier: None,
295            value: rest.to_string(),
296        });
297    }
298    if let Some(rest) = value.strip_prefix("gte.") {
299        return Ok(Operation::Quant {
300            op: QuantOperator::GreaterThanEqual,
301            quantifier: None,
302            value: rest.to_string(),
303        });
304    }
305    if let Some(rest) = value.strip_prefix("lt.") {
306        return Ok(Operation::Quant {
307            op: QuantOperator::LessThan,
308            quantifier: None,
309            value: rest.to_string(),
310        });
311    }
312    if let Some(rest) = value.strip_prefix("lte.") {
313        return Ok(Operation::Quant {
314            op: QuantOperator::LessThanEqual,
315            quantifier: None,
316            value: rest.to_string(),
317        });
318    }
319    if let Some(rest) = value.strip_prefix("like.") {
320        return Ok(Operation::Quant {
321            op: QuantOperator::Like,
322            quantifier: None,
323            value: rest.to_string(),
324        });
325    }
326    if let Some(rest) = value.strip_prefix("ilike.") {
327        return Ok(Operation::Quant {
328            op: QuantOperator::ILike,
329            quantifier: None,
330            value: rest.to_string(),
331        });
332    }
333    if let Some(rest) = value.strip_prefix("match.") {
334        return Ok(Operation::Quant {
335            op: QuantOperator::Match,
336            quantifier: None,
337            value: rest.to_string(),
338        });
339    }
340    if let Some(rest) = value.strip_prefix("imatch.") {
341        return Ok(Operation::Quant {
342            op: QuantOperator::IMatch,
343            quantifier: None,
344            value: rest.to_string(),
345        });
346    }
347
348    // Array/Range operators
349    if let Some(rest) = value.strip_prefix("cs.") {
350        return Ok(Operation::Simple {
351            op: SimpleOperator::Contains,
352            value: rest.to_string(),
353        });
354    }
355    if let Some(rest) = value.strip_prefix("cd.") {
356        return Ok(Operation::Simple {
357            op: SimpleOperator::Contained,
358            value: rest.to_string(),
359        });
360    }
361    if let Some(rest) = value.strip_prefix("ov.") {
362        return Ok(Operation::Simple {
363            op: SimpleOperator::Overlap,
364            value: rest.to_string(),
365        });
366    }
367    if let Some(rest) = value.strip_prefix("sl.") {
368        return Ok(Operation::Simple {
369            op: SimpleOperator::StrictlyLeft,
370            value: rest.to_string(),
371        });
372    }
373    if let Some(rest) = value.strip_prefix("sr.") {
374        return Ok(Operation::Simple {
375            op: SimpleOperator::StrictlyRight,
376            value: rest.to_string(),
377        });
378    }
379    if let Some(rest) = value.strip_prefix("nxr.") {
380        return Ok(Operation::Simple {
381            op: SimpleOperator::NotExtendsRight,
382            value: rest.to_string(),
383        });
384    }
385    if let Some(rest) = value.strip_prefix("nxl.") {
386        return Ok(Operation::Simple {
387            op: SimpleOperator::NotExtendsLeft,
388            value: rest.to_string(),
389        });
390    }
391    if let Some(rest) = value.strip_prefix("adj.") {
392        return Ok(Operation::Simple {
393            op: SimpleOperator::Adjacent,
394            value: rest.to_string(),
395        });
396    }
397
398    // IN operator
399    if let Some(rest) = value.strip_prefix("in.") {
400        let values = parse_in_list(rest)?;
401        return Ok(Operation::In(values));
402    }
403
404    // IS operator
405    if let Some(rest) = value.strip_prefix("is.") {
406        let is_val = match rest {
407            "null" => IsValue::Null,
408            "true" => IsValue::True,
409            "false" => IsValue::False,
410            "unknown" => IsValue::Unknown,
411            _ => return Err(Error::InvalidQueryParam(format!("is.{}", rest))),
412        };
413        return Ok(Operation::Is(is_val));
414    }
415
416    // IS DISTINCT FROM
417    if let Some(rest) = value.strip_prefix("isdistinct.") {
418        return Ok(Operation::IsDistinctFrom(rest.to_string()));
419    }
420
421    // Full-text search
422    if let Some(rest) = value.strip_prefix("fts") {
423        return parse_fts(FtsOperator::Fts, rest);
424    }
425    if let Some(rest) = value.strip_prefix("plfts") {
426        return parse_fts(FtsOperator::Plain, rest);
427    }
428    if let Some(rest) = value.strip_prefix("phfts") {
429        return parse_fts(FtsOperator::Phrase, rest);
430    }
431    if let Some(rest) = value.strip_prefix("wfts") {
432        return parse_fts(FtsOperator::Websearch, rest);
433    }
434
435    Err(Error::InvalidQueryParam(value.into()))
436}
437
438/// Parse IN list: `(a,b,c)` -> vec!["a", "b", "c"]
439fn parse_in_list(value: &str) -> Result<Vec<String>> {
440    let value = value
441        .strip_prefix('(')
442        .and_then(|s| s.strip_suffix(')'))
443        .ok_or_else(|| Error::InvalidQueryParam(format!("in.{}", value)))?;
444
445    Ok(value.split(',').map(|s| s.trim().to_string()).collect())
446}
447
448/// Parse FTS operation: `(language).query` or `.query`
449fn parse_fts(op: FtsOperator, rest: &str) -> Result<Operation> {
450    if let Some(rest) = rest.strip_prefix('(') {
451        // Has language specifier
452        let (lang, query) = rest
453            .split_once(").")
454            .ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
455        return Ok(Operation::Fts {
456            op,
457            language: Some(lang.to_string()),
458            value: query.to_string(),
459        });
460    }
461
462    let query = rest
463        .strip_prefix('.')
464        .ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
465    Ok(Operation::Fts {
466        op,
467        language: None,
468        value: query.to_string(),
469    })
470}
471
472// ============================================================================
473// Order Parsing
474// ============================================================================
475
476/// Parse order parameter: `col.desc.nullsfirst,col2.asc`
477fn parse_order_param(value: &str) -> Result<(EmbedPath, Vec<OrderTerm>)> {
478    let terms: Vec<OrderTerm> = value
479        .split(',')
480        .map(|s| parse_order_term(s.trim()))
481        .collect::<Result<Vec<_>>>()?;
482    Ok((vec![], terms))
483}
484
485fn parse_order_term(value: &str) -> Result<OrderTerm> {
486    let parts: Vec<&str> = value.split('.').collect();
487    if parts.is_empty() {
488        return Err(Error::InvalidQueryParam("order".into()));
489    }
490
491    let field_name = parts[0];
492    let mut direction = None;
493    let mut nulls = None;
494
495    for part in &parts[1..] {
496        match *part {
497            "asc" => direction = Some(OrderDirection::Asc),
498            "desc" => direction = Some(OrderDirection::Desc),
499            "nullsfirst" => nulls = Some(OrderNulls::First),
500            "nullslast" => nulls = Some(OrderNulls::Last),
501            _ => {}
502        }
503    }
504
505    Ok(OrderTerm::Field {
506        field: Field::simple(field_name),
507        direction,
508        nulls,
509    })
510}
511
512// ============================================================================
513// Logic Tree Parsing
514// ============================================================================
515
516/// Parse `and` or `or` parameter: `(filter1,filter2)`
517fn parse_logic_param(op: &str, value: &str) -> Result<LogicTree> {
518    let logic_op = match op {
519        "and" => LogicOperator::And,
520        "or" => LogicOperator::Or,
521        _ => return Err(Error::InvalidQueryParam(op.into())),
522    };
523
524    // Parse nested filters: (field.op.value,field2.op.value)
525    let value = value
526        .strip_prefix('(')
527        .and_then(|s| s.strip_suffix(')'))
528        .ok_or_else(|| Error::InvalidQueryParam(format!("{}={}", op, value)))?;
529
530    let children: Vec<LogicTree> = value
531        .split(',')
532        .map(|s| {
533            let (key, val) = s
534                .split_once('.')
535                .ok_or_else(|| Error::InvalidQueryParam(s.into()))?;
536            let (_, filter) = parse_filter_param(key, val)?;
537            Ok(LogicTree::Stmt(filter))
538        })
539        .collect::<Result<Vec<_>>>()?;
540
541    Ok(LogicTree::Expr {
542        negated: false,
543        op: logic_op,
544        children,
545    })
546}
547
548// ============================================================================
549// Helper Parsers
550// ============================================================================
551
552fn parse_identifier(input: &str) -> IResult<&str, &str> {
553    take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)
554}
555
556fn parse_json_path(input: &str) -> IResult<&str, JsonPath> {
557    many0(alt((parse_arrow, parse_double_arrow)))(input)
558}
559
560fn parse_arrow(input: &str) -> IResult<&str, JsonOperation> {
561    let (input, _) = tag("->")(input)?;
562    let (input, operand) = alt((
563        map(digit1, |s: &str| {
564            JsonOperand::Idx(s.parse().unwrap_or(0))
565        }),
566        map(parse_identifier, |s| JsonOperand::Key(s.to_string())),
567    ))(input)?;
568    Ok((input, JsonOperation::Arrow(operand)))
569}
570
571fn parse_double_arrow(input: &str) -> IResult<&str, JsonOperation> {
572    let (input, _) = tag("->>")(input)?;
573    let (input, operand) = alt((
574        map(digit1, |s: &str| {
575            JsonOperand::Idx(s.parse().unwrap_or(0))
576        }),
577        map(parse_identifier, |s| JsonOperand::Key(s.to_string())),
578    ))(input)?;
579    Ok((input, JsonOperation::DoubleArrow(operand)))
580}
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    #[test]
587    fn test_parse_simple_filter() {
588        let params = parse_query_params("name=eq.John").unwrap();
589        assert_eq!(params.filters_root.len(), 1);
590        assert_eq!(params.filters_root[0].field.name, "name");
591    }
592
593    #[test]
594    fn test_parse_negated_filter() {
595        let params = parse_query_params("status=not.eq.active").unwrap();
596        assert!(params.filters_root[0].op_expr.negated);
597    }
598
599    #[test]
600    fn test_parse_in_filter() {
601        let params = parse_query_params("id=in.(1,2,3)").unwrap();
602        match &params.filters_root[0].op_expr.operation {
603            Operation::In(values) => {
604                assert_eq!(values, &vec!["1", "2", "3"]);
605            }
606            _ => panic!("Expected In operation"),
607        }
608    }
609
610    #[test]
611    fn test_parse_is_null() {
612        let params = parse_query_params("deleted_at=is.null").unwrap();
613        match &params.filters_root[0].op_expr.operation {
614            Operation::Is(IsValue::Null) => {}
615            _ => panic!("Expected Is Null"),
616        }
617    }
618
619    #[test]
620    fn test_parse_order() {
621        let params = parse_query_params("order=name.asc,age.desc.nullslast").unwrap();
622        assert_eq!(params.order.len(), 1);
623        let (_, terms) = &params.order[0];
624        assert_eq!(terms.len(), 2);
625    }
626
627    #[test]
628    fn test_parse_limit_offset() {
629        let params = parse_query_params("limit=10&offset=20").unwrap();
630        let range = params.ranges.get("").unwrap();
631        assert_eq!(range.limit, Some(10));
632        assert_eq!(range.offset, 20);
633    }
634
635    #[test]
636    fn test_parse_select() {
637        let items = parse_select("id,name,orders(id,amount)").unwrap();
638        assert_eq!(items.len(), 3);
639    }
640
641    #[test]
642    fn test_parse_fts() {
643        let params = parse_query_params("content=fts(english).search+term").unwrap();
644        match &params.filters_root[0].op_expr.operation {
645            Operation::Fts { op, language, value } => {
646                assert_eq!(*op, FtsOperator::Fts);
647                assert_eq!(language.as_deref(), Some("english"));
648                assert_eq!(value, "search+term");
649            }
650            _ => panic!("Expected FTS operation"),
651        }
652    }
653}