api_tools/value_objects/
query_sort.rs

1//! Query sorts value object representation
2
3use std::fmt::Display;
4
5/// Filter sort field
6pub type QuerySortField = String;
7
8/// Filter sort direction (ASC or DESC)
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
10pub enum QuerySortDirection {
11    /// Ascending sort (`'+'` prefix)
12    /// Example: `?sort=+id`
13    #[default]
14    Asc,
15
16    /// Descending sort (`'-'` prefix)
17    /// Example: `?sort=-name`
18    Desc,
19}
20
21impl Display for QuerySortDirection {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(
24            f,
25            "{}",
26            match self {
27                Self::Asc => "ASC",
28                Self::Desc => "DESC",
29            }
30        )
31    }
32}
33
34#[derive(Debug, Clone, Default, PartialEq, Eq)]
35pub struct QuerySort {
36    pub field: QuerySortField,
37    pub direction: QuerySortDirection,
38}
39
40impl QuerySort {
41    /// Create a new query sort
42    pub fn new(field: QuerySortField, direction: QuerySortDirection) -> Self {
43        Self { field, direction }
44    }
45}
46
47/// Filter sorts
48#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct QuerySorts(pub Vec<QuerySort>);
50
51impl From<&str> for QuerySorts {
52    /// Create a new query sorting from a string
53    ///
54    /// # Example
55    /// ```
56    /// use api_tools::value_objects::query_sort::{QuerySort, QuerySortDirection, QuerySorts};
57    ///
58    /// let sorts = QuerySorts::from("+id,-name");
59    /// assert_eq!(
60    ///     sorts.0,
61    ///     vec![
62    ///         QuerySort {
63    ///             field: "id".to_string(),
64    ///             direction: QuerySortDirection::Asc
65    ///         },
66    ///         QuerySort {
67    ///             field: "name".to_string(),
68    ///             direction: QuerySortDirection::Desc
69    ///         },
70    ///     ]
71    /// );
72    /// ```
73    fn from(value: &str) -> Self {
74        let mut sorts = Vec::new();
75        let parts = value.split(',');
76
77        for part in parts {
78            let prefix = part.chars().next();
79            if let Some(prefix) = prefix {
80                if prefix == '+' {
81                    sorts.push(QuerySort::new(part[1..].to_string(), QuerySortDirection::Asc));
82                } else if prefix == '-' {
83                    sorts.push(QuerySort::new(part[1..].to_string(), QuerySortDirection::Desc));
84                }
85            }
86        }
87
88        Self(sorts)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_query_sort_direction_default() {
98        assert_eq!(QuerySortDirection::default(), QuerySortDirection::Asc);
99    }
100
101    #[test]
102    fn test_query_sort_direction_display() {
103        assert_eq!("ASC", QuerySortDirection::Asc.to_string());
104        assert_eq!("DESC", QuerySortDirection::Desc.to_string());
105    }
106
107    #[test]
108    fn test_filter_sorts_from_str() {
109        let sorts = QuerySorts::from("");
110        assert!(sorts.0.is_empty());
111
112        let sorts = QuerySorts::from("+id,-name");
113        assert_eq!(sorts.0.len(), 2);
114        assert_eq!(
115            sorts.0,
116            vec![
117                QuerySort {
118                    field: "id".to_string(),
119                    direction: QuerySortDirection::Asc
120                },
121                QuerySort {
122                    field: "name".to_string(),
123                    direction: QuerySortDirection::Desc
124                },
125            ]
126        );
127
128        let sorts = QuerySorts::from("id");
129        assert!(sorts.0.is_empty());
130    }
131}