Skip to main content

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]
259        .iter()
260        .map(|s| s.to_string())
261        .collect();
262    let field = parts.last().unwrap().to_string();
263    Ok((path, field))
264}
265
266/// Parse filter value: `operator.value` or `not.operator.value`
267fn parse_filter_value(value: &str) -> Result<OpExpr> {
268    let (value, negated) = if let Some(rest) = value.strip_prefix("not.") {
269        (rest, true)
270    } else {
271        (value, false)
272    };
273
274    let operation = parse_operation(value)?;
275    Ok(OpExpr { negated, operation })
276}
277
278/// Parse an operation: `eq.value`, `in.(a,b,c)`, `is.null`, etc.
279fn parse_operation(value: &str) -> Result<Operation> {
280    // Try each operator pattern
281    if let Some(rest) = value.strip_prefix("eq.") {
282        return Ok(Operation::Quant {
283            op: QuantOperator::Equal,
284            quantifier: None,
285            value: rest.to_string(),
286        });
287    }
288    if let Some(rest) = value.strip_prefix("neq.") {
289        return Ok(Operation::Simple {
290            op: SimpleOperator::NotEqual,
291            value: rest.to_string(),
292        });
293    }
294    if let Some(rest) = value.strip_prefix("gt.") {
295        return Ok(Operation::Quant {
296            op: QuantOperator::GreaterThan,
297            quantifier: None,
298            value: rest.to_string(),
299        });
300    }
301    if let Some(rest) = value.strip_prefix("gte.") {
302        return Ok(Operation::Quant {
303            op: QuantOperator::GreaterThanEqual,
304            quantifier: None,
305            value: rest.to_string(),
306        });
307    }
308    if let Some(rest) = value.strip_prefix("lt.") {
309        return Ok(Operation::Quant {
310            op: QuantOperator::LessThan,
311            quantifier: None,
312            value: rest.to_string(),
313        });
314    }
315    if let Some(rest) = value.strip_prefix("lte.") {
316        return Ok(Operation::Quant {
317            op: QuantOperator::LessThanEqual,
318            quantifier: None,
319            value: rest.to_string(),
320        });
321    }
322    if let Some(rest) = value.strip_prefix("like.") {
323        return Ok(Operation::Quant {
324            op: QuantOperator::Like,
325            quantifier: None,
326            value: rest.to_string(),
327        });
328    }
329    if let Some(rest) = value.strip_prefix("ilike.") {
330        return Ok(Operation::Quant {
331            op: QuantOperator::ILike,
332            quantifier: None,
333            value: rest.to_string(),
334        });
335    }
336    if let Some(rest) = value.strip_prefix("match.") {
337        return Ok(Operation::Quant {
338            op: QuantOperator::Match,
339            quantifier: None,
340            value: rest.to_string(),
341        });
342    }
343    if let Some(rest) = value.strip_prefix("imatch.") {
344        return Ok(Operation::Quant {
345            op: QuantOperator::IMatch,
346            quantifier: None,
347            value: rest.to_string(),
348        });
349    }
350
351    // Array/Range operators
352    if let Some(rest) = value.strip_prefix("cs.") {
353        return Ok(Operation::Simple {
354            op: SimpleOperator::Contains,
355            value: rest.to_string(),
356        });
357    }
358    if let Some(rest) = value.strip_prefix("cd.") {
359        return Ok(Operation::Simple {
360            op: SimpleOperator::Contained,
361            value: rest.to_string(),
362        });
363    }
364    if let Some(rest) = value.strip_prefix("ov.") {
365        return Ok(Operation::Simple {
366            op: SimpleOperator::Overlap,
367            value: rest.to_string(),
368        });
369    }
370    if let Some(rest) = value.strip_prefix("sl.") {
371        return Ok(Operation::Simple {
372            op: SimpleOperator::StrictlyLeft,
373            value: rest.to_string(),
374        });
375    }
376    if let Some(rest) = value.strip_prefix("sr.") {
377        return Ok(Operation::Simple {
378            op: SimpleOperator::StrictlyRight,
379            value: rest.to_string(),
380        });
381    }
382    if let Some(rest) = value.strip_prefix("nxr.") {
383        return Ok(Operation::Simple {
384            op: SimpleOperator::NotExtendsRight,
385            value: rest.to_string(),
386        });
387    }
388    if let Some(rest) = value.strip_prefix("nxl.") {
389        return Ok(Operation::Simple {
390            op: SimpleOperator::NotExtendsLeft,
391            value: rest.to_string(),
392        });
393    }
394    if let Some(rest) = value.strip_prefix("adj.") {
395        return Ok(Operation::Simple {
396            op: SimpleOperator::Adjacent,
397            value: rest.to_string(),
398        });
399    }
400
401    // IN operator
402    if let Some(rest) = value.strip_prefix("in.") {
403        let values = parse_in_list(rest)?;
404        return Ok(Operation::In(values));
405    }
406
407    // IS operator
408    if let Some(rest) = value.strip_prefix("is.") {
409        let is_val = match rest {
410            "null" => IsValue::Null,
411            "true" => IsValue::True,
412            "false" => IsValue::False,
413            "unknown" => IsValue::Unknown,
414            _ => return Err(Error::InvalidQueryParam(format!("is.{}", rest))),
415        };
416        return Ok(Operation::Is(is_val));
417    }
418
419    // IS DISTINCT FROM
420    if let Some(rest) = value.strip_prefix("isdistinct.") {
421        return Ok(Operation::IsDistinctFrom(rest.to_string()));
422    }
423
424    // Full-text search
425    if let Some(rest) = value.strip_prefix("fts") {
426        return parse_fts(FtsOperator::Fts, rest);
427    }
428    if let Some(rest) = value.strip_prefix("plfts") {
429        return parse_fts(FtsOperator::Plain, rest);
430    }
431    if let Some(rest) = value.strip_prefix("phfts") {
432        return parse_fts(FtsOperator::Phrase, rest);
433    }
434    if let Some(rest) = value.strip_prefix("wfts") {
435        return parse_fts(FtsOperator::Websearch, rest);
436    }
437
438    Err(Error::InvalidQueryParam(value.into()))
439}
440
441/// Parse IN list: `(a,b,c)` -> vec!["a", "b", "c"]
442fn parse_in_list(value: &str) -> Result<Vec<String>> {
443    let value = value
444        .strip_prefix('(')
445        .and_then(|s| s.strip_suffix(')'))
446        .ok_or_else(|| Error::InvalidQueryParam(format!("in.{}", value)))?;
447
448    Ok(value.split(',').map(|s| s.trim().to_string()).collect())
449}
450
451/// Parse FTS operation: `(language).query` or `.query`
452fn parse_fts(op: FtsOperator, rest: &str) -> Result<Operation> {
453    if let Some(rest) = rest.strip_prefix('(') {
454        // Has language specifier
455        let (lang, query) = rest
456            .split_once(").")
457            .ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
458        return Ok(Operation::Fts {
459            op,
460            language: Some(lang.to_string()),
461            value: query.to_string(),
462        });
463    }
464
465    let query = rest
466        .strip_prefix('.')
467        .ok_or_else(|| Error::InvalidQueryParam(format!("fts{}", rest)))?;
468    Ok(Operation::Fts {
469        op,
470        language: None,
471        value: query.to_string(),
472    })
473}
474
475// ============================================================================
476// Order Parsing
477// ============================================================================
478
479/// Parse order parameter: `col.desc.nullsfirst,col2.asc`
480fn parse_order_param(value: &str) -> Result<(EmbedPath, Vec<OrderTerm>)> {
481    let terms: Vec<OrderTerm> = value
482        .split(',')
483        .map(|s| parse_order_term(s.trim()))
484        .collect::<Result<Vec<_>>>()?;
485    Ok((vec![], terms))
486}
487
488fn parse_order_term(value: &str) -> Result<OrderTerm> {
489    let parts: Vec<&str> = value.split('.').collect();
490    if parts.is_empty() {
491        return Err(Error::InvalidQueryParam("order".into()));
492    }
493
494    let field_name = parts[0];
495    let mut direction = None;
496    let mut nulls = None;
497
498    for part in &parts[1..] {
499        match *part {
500            "asc" => direction = Some(OrderDirection::Asc),
501            "desc" => direction = Some(OrderDirection::Desc),
502            "nullsfirst" => nulls = Some(OrderNulls::First),
503            "nullslast" => nulls = Some(OrderNulls::Last),
504            _ => {}
505        }
506    }
507
508    Ok(OrderTerm::Field {
509        field: Field::simple(field_name),
510        direction,
511        nulls,
512    })
513}
514
515// ============================================================================
516// Logic Tree Parsing
517// ============================================================================
518
519/// Parse `and` or `or` parameter: `(filter1,filter2)`
520fn parse_logic_param(op: &str, value: &str) -> Result<LogicTree> {
521    let logic_op = match op {
522        "and" => LogicOperator::And,
523        "or" => LogicOperator::Or,
524        _ => return Err(Error::InvalidQueryParam(op.into())),
525    };
526
527    // Parse nested filters: (field.op.value,field2.op.value)
528    let value = value
529        .strip_prefix('(')
530        .and_then(|s| s.strip_suffix(')'))
531        .ok_or_else(|| Error::InvalidQueryParam(format!("{}={}", op, value)))?;
532
533    let children: Vec<LogicTree> = value
534        .split(',')
535        .map(|s| {
536            let (key, val) = s
537                .split_once('.')
538                .ok_or_else(|| Error::InvalidQueryParam(s.into()))?;
539            let (_, filter) = parse_filter_param(key, val)?;
540            Ok(LogicTree::Stmt(filter))
541        })
542        .collect::<Result<Vec<_>>>()?;
543
544    Ok(LogicTree::Expr {
545        negated: false,
546        op: logic_op,
547        children,
548    })
549}
550
551// ============================================================================
552// Helper Parsers
553// ============================================================================
554
555fn parse_identifier(input: &str) -> IResult<&str, &str> {
556    take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)
557}
558
559fn parse_json_path(input: &str) -> IResult<&str, JsonPath> {
560    many0(alt((parse_arrow, parse_double_arrow)))(input)
561}
562
563fn parse_arrow(input: &str) -> IResult<&str, JsonOperation> {
564    let (input, _) = tag("->")(input)?;
565    let (input, operand) = alt((
566        map(digit1, |s: &str| JsonOperand::Idx(s.parse().unwrap_or(0))),
567        map(parse_identifier, |s| JsonOperand::Key(s.to_string())),
568    ))(input)?;
569    Ok((input, JsonOperation::Arrow(operand)))
570}
571
572fn parse_double_arrow(input: &str) -> IResult<&str, JsonOperation> {
573    let (input, _) = tag("->>")(input)?;
574    let (input, operand) = alt((
575        map(digit1, |s: &str| JsonOperand::Idx(s.parse().unwrap_or(0))),
576        map(parse_identifier, |s| JsonOperand::Key(s.to_string())),
577    ))(input)?;
578    Ok((input, JsonOperation::DoubleArrow(operand)))
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584
585    #[test]
586    fn test_parse_simple_filter() {
587        let params = parse_query_params("name=eq.John").unwrap();
588        assert_eq!(params.filters_root.len(), 1);
589        assert_eq!(params.filters_root[0].field.name, "name");
590    }
591
592    #[test]
593    fn test_parse_negated_filter() {
594        let params = parse_query_params("status=not.eq.active").unwrap();
595        assert!(params.filters_root[0].op_expr.negated);
596    }
597
598    #[test]
599    fn test_parse_in_filter() {
600        let params = parse_query_params("id=in.(1,2,3)").unwrap();
601        match &params.filters_root[0].op_expr.operation {
602            Operation::In(values) => {
603                assert_eq!(values, &vec!["1", "2", "3"]);
604            }
605            _ => panic!("Expected In operation"),
606        }
607    }
608
609    #[test]
610    fn test_parse_is_null() {
611        let params = parse_query_params("deleted_at=is.null").unwrap();
612        match &params.filters_root[0].op_expr.operation {
613            Operation::Is(IsValue::Null) => {}
614            _ => panic!("Expected Is Null"),
615        }
616    }
617
618    #[test]
619    fn test_parse_order() {
620        let params = parse_query_params("order=name.asc,age.desc.nullslast").unwrap();
621        assert_eq!(params.order.len(), 1);
622        let (_, terms) = &params.order[0];
623        assert_eq!(terms.len(), 2);
624    }
625
626    #[test]
627    fn test_parse_limit_offset() {
628        let params = parse_query_params("limit=10&offset=20").unwrap();
629        let range = params.ranges.get("").unwrap();
630        assert_eq!(range.limit, Some(10));
631        assert_eq!(range.offset, 20);
632    }
633
634    #[test]
635    fn test_parse_select() {
636        let items = parse_select("id,name,orders(id,amount)").unwrap();
637        assert_eq!(items.len(), 3);
638    }
639
640    #[test]
641    fn test_parse_fts() {
642        let params = parse_query_params("content=fts(english).search+term").unwrap();
643        match &params.filters_root[0].op_expr.operation {
644            Operation::Fts {
645                op,
646                language,
647                value,
648            } => {
649                assert_eq!(*op, FtsOperator::Fts);
650                assert_eq!(language.as_deref(), Some("english"));
651                assert_eq!(value, "search+term");
652            }
653            _ => panic!("Expected FTS operation"),
654        }
655    }
656}