junobuild_shared/
list.rs

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