Skip to main content

postgrest_parser/parser/
order.rs

1use super::common::{field, parse_field_fallback};
2use crate::ast::{Direction, Field, Nulls, OrderTerm};
3use crate::error::ParseError;
4
5fn is_direction(s: &str) -> bool {
6    matches!(s, "asc" | "desc")
7}
8
9fn is_nulls_option(s: &str) -> bool {
10    matches!(s, "nullsfirst" | "nullslast")
11}
12
13/// Parses a PostgREST order clause into a list of order terms.
14///
15/// # Syntax
16///
17/// - Single column: `column_name.asc` or `column_name.desc`
18/// - Multiple columns: `col1.asc,col2.desc,col3`
19/// - With nulls handling: `column.desc.nullsfirst` or `column.asc.nullslast`
20/// - JSON fields: `data->created_at.desc`
21/// - Type casts: `price::numeric.desc`
22///
23/// # Examples
24///
25/// ```
26/// use postgrest_parser::parse_order;
27///
28/// // Single column ascending
29/// let terms = parse_order("name.asc").unwrap();
30/// assert_eq!(terms.len(), 1);
31///
32/// // Multiple columns
33/// let terms = parse_order("created_at.desc,name.asc").unwrap();
34/// assert_eq!(terms.len(), 2);
35///
36/// // With nulls handling
37/// let terms = parse_order("updated_at.desc.nullsfirst").unwrap();
38/// assert_eq!(terms.len(), 1);
39///
40/// // JSON field ordering
41/// let terms = parse_order("data->timestamp.desc").unwrap();
42/// assert_eq!(terms.len(), 1);
43///
44/// // Default direction (ascending)
45/// let terms = parse_order("id").unwrap();
46/// assert_eq!(terms.len(), 1);
47/// ```
48///
49/// # Errors
50///
51/// Returns `ParseError` if:
52/// - Field name is invalid or empty
53/// - Direction is not `asc` or `desc`
54/// - Nulls option is not `nullsfirst` or `nullslast`
55pub fn parse_order(order_str: &str) -> Result<Vec<OrderTerm>, ParseError> {
56    if order_str.is_empty() || order_str.trim().is_empty() {
57        return Ok(Vec::new());
58    }
59
60    let items: Vec<&str> = order_str.split(',').map(|s| s.trim()).collect();
61
62    items
63        .iter()
64        .map(|item_str| parse_order_term(item_str))
65        .collect()
66}
67
68/// Parses a single order term from a string.
69///
70/// # Examples
71///
72/// ```
73/// use postgrest_parser::{parse_order_term, Direction};
74///
75/// let term = parse_order_term("created_at.desc").unwrap();
76/// assert_eq!(term.field.name, "created_at");
77/// assert_eq!(term.direction, Direction::Desc);
78/// ```
79pub fn parse_order_term(term_str: &str) -> Result<OrderTerm, ParseError> {
80    let parts: Vec<&str> = term_str.split('.').collect();
81
82    if parts.is_empty() || parts[0].is_empty() {
83        return Err(ParseError::InvalidOrderOptions(term_str.to_string()));
84    }
85
86    let (field_parts, option_parts) = split_field_and_options(&parts);
87
88    let field_str = field_parts.join(".");
89    let field = parse_order_field(&field_str)?;
90
91    let (direction, nulls) = parse_options(&option_parts)?;
92
93    let mut term = OrderTerm::new(field).with_direction(direction);
94    if let Some(n) = nulls {
95        term = term.with_nulls(n);
96    }
97
98    Ok(term)
99}
100
101fn split_field_and_options(parts: &[&str]) -> (Vec<String>, Vec<String>) {
102    if parts.is_empty() {
103        return (Vec::new(), Vec::new());
104    }
105
106    let mut field_parts = vec![parts[0].to_string()];
107    let mut option_parts = Vec::new();
108    let mut seen_option = false;
109
110    for part in &parts[1..] {
111        if is_direction(part) || is_nulls_option(part) {
112            option_parts.push(part.to_string());
113            seen_option = true;
114        } else if !seen_option && (part.contains("->") || part.contains("::")) {
115            field_parts.push(part.to_string());
116        } else {
117            option_parts.push(part.to_string());
118        }
119    }
120
121    (field_parts, option_parts)
122}
123
124fn parse_order_field(field_str: &str) -> Result<Field, ParseError> {
125    match field(field_str) {
126        Ok((_, field)) => Ok(field),
127        Err(_) => parse_field_fallback(field_str),
128    }
129}
130
131fn parse_options(option_parts: &[String]) -> Result<(Direction, Option<Nulls>), ParseError> {
132    let mut direction = Direction::Asc;
133    let mut nulls: Option<Nulls> = None;
134
135    for part in option_parts {
136        let lower = part.to_lowercase();
137        match lower.as_str() {
138            "asc" => direction = Direction::Asc,
139            "desc" => direction = Direction::Desc,
140            "nullsfirst" => nulls = Some(Nulls::First),
141            "nullslast" => nulls = Some(Nulls::Last),
142            _ => return Err(ParseError::InvalidOrderOptions(lower.to_string())),
143        }
144    }
145
146    Ok((direction, nulls))
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_parse_order_simple() {
155        let result = parse_order("id");
156        assert!(result.is_ok());
157        let terms = result.unwrap();
158        assert_eq!(terms.len(), 1);
159        assert_eq!(terms[0].direction, Direction::Asc);
160    }
161
162    #[test]
163    fn test_parse_order_desc() {
164        let result = parse_order("id.desc");
165        assert!(result.is_ok());
166        let terms = result.unwrap();
167        assert_eq!(terms[0].direction, Direction::Desc);
168    }
169
170    #[test]
171    fn test_parse_order_with_nulls() {
172        let result = parse_order("id.desc.nullslast");
173        assert!(result.is_ok());
174        let terms = result.unwrap();
175        assert_eq!(terms[0].direction, Direction::Desc);
176        assert_eq!(terms[0].nulls, Some(Nulls::Last));
177    }
178
179    #[test]
180    fn test_parse_order_multiple() {
181        let result = parse_order("id.desc,name.asc");
182        assert!(result.is_ok());
183        let terms = result.unwrap();
184        assert_eq!(terms.len(), 2);
185        assert_eq!(terms[0].direction, Direction::Desc);
186        assert_eq!(terms[1].direction, Direction::Asc);
187    }
188
189    #[test]
190    fn test_parse_order_with_json_path() {
191        let result = parse_order("data->key.desc");
192        assert!(result.is_ok());
193        let terms = result.unwrap();
194        assert_eq!(terms[0].field.name, "data");
195        assert_eq!(terms[0].field.json_path.len(), 1);
196    }
197
198    #[test]
199    fn test_parse_order_empty() {
200        let result = parse_order("");
201        assert!(result.is_ok());
202        assert!(result.unwrap().is_empty());
203    }
204
205    #[test]
206    fn test_parse_order_invalid() {
207        let result = parse_order("id.invalid");
208        assert!(matches!(result, Err(ParseError::InvalidOrderOptions(_))));
209    }
210
211    #[test]
212    fn test_parse_order_with_cast() {
213        let result = parse_order("price::numeric.desc");
214        assert!(result.is_ok());
215        let terms = result.unwrap();
216        assert_eq!(terms[0].field.cast, Some("numeric".to_string()));
217    }
218}