junobuild_shared/
list.rs

1use crate::regex::build_regex;
2use crate::types::core::Key;
3use crate::types::list::{
4    ListMatcher, ListOrder, ListOrderField, ListPaginate, ListParams, ListResults, TimestampMatcher,
5};
6use crate::types::state::Timestamp;
7use crate::types::state::Timestamped;
8use regex::Regex;
9
10pub fn list_values<'a, T: Clone + Timestamped>(
11    matches: &'a [(&'a Key, &'a T)],
12    filters: &'a ListParams,
13) -> ListResults<T> {
14    let matches_length = matches.len();
15
16    let ordered = order_values(matches, filters);
17
18    let start = start_at(&ordered, filters);
19
20    let paginated = paginate_values(ordered, filters, &start);
21
22    let length = paginated.len();
23
24    ListResults {
25        items: paginated,
26        items_length: length,
27        matches_length,
28        items_page: current_page(start, filters),
29        matches_pages: total_pages(matches_length, filters),
30    }
31}
32
33fn current_page(start_at: Option<usize>, filters: &ListParams) -> Option<usize> {
34    match start_at {
35        None => None,
36        Some(start_at) => match filters.clone().paginate {
37            None => None,
38            Some(paginate) => paginate.limit.map(|limit| start_at / limit),
39        },
40    }
41}
42
43fn total_pages(matches_length: usize, filters: &ListParams) -> Option<usize> {
44    match filters.clone().paginate {
45        None => None,
46        Some(paginate) => paginate.limit.map(|limit| matches_length / limit),
47    }
48}
49
50fn start_at<T: Clone + Timestamped>(matches: &[(&Key, &T)], filters: &ListParams) -> Option<usize> {
51    match filters.clone().paginate {
52        None => None,
53        Some(paginate) => match paginate.start_after {
54            None => Some(0),
55            Some(start_after) => {
56                let index = matches
57                    .iter()
58                    .position(|(key, _)| (*key).clone().eq(&start_after));
59                index.map(|index| index + 1)
60            }
61        },
62    }
63}
64
65fn order_values<'a, T: Clone + Timestamped>(
66    matches: &'a [(&'a Key, &'a T)],
67    ListParams {
68        matcher: _,
69        order,
70        paginate: _,
71        owner: _,
72    }: &'a ListParams,
73) -> Vec<(&'a Key, &'a T)> {
74    match order {
75        None => matches.to_vec(),
76        Some(ListOrder { desc, field }) => match field {
77            ListOrderField::Keys => order_values_with_keys(matches, desc),
78            ListOrderField::UpdatedAt => order_values_with_updated_at(matches, desc),
79            ListOrderField::CreatedAt => order_values_with_created_at(matches, desc),
80        },
81    }
82}
83
84fn order_values_with_updated_at<'a, T: Clone + Timestamped>(
85    matches: &'a [(&'a Key, &'a T)],
86    desc: &bool,
87) -> Vec<(&'a Key, &'a T)> {
88    let mut sorted_matches = matches.to_vec();
89
90    if *desc {
91        sorted_matches.sort_by(|(_, value_a), (_, value_b)| value_b.cmp_updated_at(value_a));
92        return sorted_matches;
93    }
94
95    sorted_matches.sort_by(|(_, value_a), (_, value_b)| value_a.cmp_updated_at(value_b));
96    sorted_matches
97}
98
99fn order_values_with_created_at<'a, T: Clone + Timestamped>(
100    matches: &'a [(&'a Key, &'a T)],
101    desc: &bool,
102) -> Vec<(&'a Key, &'a T)> {
103    let mut sorted_matches = matches.to_vec();
104
105    if *desc {
106        sorted_matches.sort_by(|(_, value_a), (_, value_b)| value_b.cmp_created_at(value_a));
107        return sorted_matches;
108    }
109
110    sorted_matches.sort_by(|(_, value_a), (_, value_b)| value_a.cmp_created_at(value_b));
111    sorted_matches
112}
113
114fn order_values_with_keys<'a, T: Clone + Timestamped>(
115    matches: &'a [(&'a Key, &'a T)],
116    desc: &bool,
117) -> Vec<(&'a Key, &'a T)> {
118    let mut sorted_matches = matches.to_vec();
119
120    if *desc {
121        sorted_matches.sort_by(|(key_a, _), (key_b, _)| key_b.cmp(key_a));
122        return sorted_matches;
123    }
124
125    sorted_matches.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
126    sorted_matches
127}
128
129fn paginate_values<T: Clone + Timestamped>(
130    matches: Vec<(&Key, &T)>,
131    ListParams {
132        matcher: _,
133        order: _,
134        paginate,
135        owner: _,
136    }: &ListParams,
137    start_at: &Option<usize>,
138) -> Vec<(Key, T)> {
139    match paginate {
140        None => matches
141            .iter()
142            .map(|(key, value)| ((*key).clone(), (*value).clone()))
143            .collect(),
144        Some(ListPaginate {
145            start_after: _,
146            limit,
147        }) => {
148            let max: usize = matches.len();
149
150            if max == 0 {
151                return Vec::new();
152            }
153
154            let length = match limit {
155                None => max,
156                Some(limit) => {
157                    if *limit > max {
158                        max
159                    } else {
160                        *limit
161                    }
162                }
163            };
164
165            let start = match *start_at {
166                None => {
167                    return Vec::new();
168                }
169                Some(start_at) => start_at,
170            };
171
172            if start > (max - 1) {
173                return Vec::new();
174            }
175
176            if start.saturating_add(length) > max - 1 {
177                return matches[start..=(max - 1)]
178                    .iter()
179                    .map(|(key, value)| ((*key).clone(), (*value).clone()))
180                    .collect();
181            }
182
183            matches[start..=(start + length - 1)]
184                .iter()
185                .map(|(key, value)| ((*key).clone(), (*value).clone()))
186                .collect()
187        }
188    }
189}
190
191pub fn matcher_regex(
192    matcher: &Option<ListMatcher>,
193) -> Result<(Option<Regex>, Option<Regex>), String> {
194    let regex_key: Option<Regex> = match matcher {
195        None => None,
196        Some(matcher) => matcher
197            .key
198            .as_ref()
199            .map(|filter| build_regex(filter))
200            .transpose()?,
201    };
202
203    let regex_description: Option<Regex> = match matcher {
204        None => None,
205        Some(matcher) => matcher
206            .description
207            .as_ref()
208            .map(|filter| build_regex(filter))
209            .transpose()?,
210    };
211
212    Ok((regex_key, regex_description))
213}
214
215pub fn filter_timestamps<T: Timestamped>(matcher: &Option<ListMatcher>, item: &T) -> bool {
216    if let Some(matcher) = matcher {
217        if let Some(ref created_at_filter) = matcher.created_at {
218            if !match_timestamp(item.created_at(), created_at_filter) {
219                return false;
220            }
221        }
222
223        if let Some(ref updated_at_filter) = matcher.updated_at {
224            if !match_timestamp(item.updated_at(), updated_at_filter) {
225                return false;
226            }
227        }
228    }
229
230    true
231}
232
233fn match_timestamp(timestamp: Timestamp, filter: &TimestampMatcher) -> bool {
234    match filter {
235        TimestampMatcher::Equal(ts) => timestamp == *ts,
236        TimestampMatcher::GreaterThan(ts) => timestamp > *ts,
237        TimestampMatcher::LessThan(ts) => timestamp < *ts,
238        TimestampMatcher::Between(start, end) => timestamp >= *start && timestamp <= *end,
239    }
240}