oxihuman_core/
sort_key.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, PartialEq)]
10pub enum SortDir {
11 Asc,
12 Desc,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct SortCriterion {
19 pub field: String,
20 pub dir: SortDir,
21 pub numeric: bool,
22}
23
24#[allow(dead_code)]
26#[derive(Debug, Clone, Default)]
27pub struct SortKey {
28 criteria: Vec<SortCriterion>,
29}
30
31#[allow(dead_code)]
32impl SortKey {
33 pub fn new() -> Self {
34 Self {
35 criteria: Vec::new(),
36 }
37 }
38
39 pub fn asc(mut self, field: &str) -> Self {
41 self.criteria.push(SortCriterion {
42 field: field.to_string(),
43 dir: SortDir::Asc,
44 numeric: false,
45 });
46 self
47 }
48
49 pub fn desc(mut self, field: &str) -> Self {
51 self.criteria.push(SortCriterion {
52 field: field.to_string(),
53 dir: SortDir::Desc,
54 numeric: false,
55 });
56 self
57 }
58
59 pub fn asc_num(mut self, field: &str) -> Self {
61 self.criteria.push(SortCriterion {
62 field: field.to_string(),
63 dir: SortDir::Asc,
64 numeric: true,
65 });
66 self
67 }
68
69 pub fn desc_num(mut self, field: &str) -> Self {
71 self.criteria.push(SortCriterion {
72 field: field.to_string(),
73 dir: SortDir::Desc,
74 numeric: true,
75 });
76 self
77 }
78
79 pub fn criterion_count(&self) -> usize {
80 self.criteria.len()
81 }
82
83 pub fn is_empty(&self) -> bool {
84 self.criteria.is_empty()
85 }
86
87 pub fn compare(
89 &self,
90 a: &std::collections::HashMap<String, String>,
91 b: &std::collections::HashMap<String, String>,
92 ) -> std::cmp::Ordering {
93 for c in &self.criteria {
94 let va = a.get(&c.field).map(|s| s.as_str()).unwrap_or("");
95 let vb = b.get(&c.field).map(|s| s.as_str()).unwrap_or("");
96 let ord = if c.numeric {
97 let na: f64 = va.parse().unwrap_or(0.0);
98 let nb: f64 = vb.parse().unwrap_or(0.0);
99 na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
100 } else {
101 va.cmp(vb)
102 };
103 let ord = if c.dir == SortDir::Desc {
104 ord.reverse()
105 } else {
106 ord
107 };
108 if ord != std::cmp::Ordering::Equal {
109 return ord;
110 }
111 }
112 std::cmp::Ordering::Equal
113 }
114
115 pub fn sort(&self, records: &mut [std::collections::HashMap<String, String>]) {
117 records.sort_by(|a, b| self.compare(a, b));
118 }
119
120 pub fn clear(&mut self) {
121 self.criteria.clear();
122 }
123
124 pub fn to_string_repr(&self) -> String {
126 self.criteria
127 .iter()
128 .map(|c| {
129 let dir = if c.dir == SortDir::Asc { "asc" } else { "desc" };
130 format!("{}:{}", c.field, dir)
131 })
132 .collect::<Vec<_>>()
133 .join(",")
134 }
135}
136
137pub fn new_sort_key() -> SortKey {
138 SortKey::new()
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::collections::HashMap;
145
146 fn rec(pairs: &[(&str, &str)]) -> HashMap<String, String> {
147 pairs
148 .iter()
149 .map(|(k, v)| (k.to_string(), v.to_string()))
150 .collect()
151 }
152
153 #[test]
154 fn asc_sort() {
155 let key = SortKey::new().asc("name");
156 let mut rows = vec![rec(&[("name", "bob")]), rec(&[("name", "alice")])];
157 key.sort(&mut rows);
158 assert_eq!(rows[0]["name"], "alice");
159 }
160
161 #[test]
162 fn desc_sort() {
163 let key = SortKey::new().desc("name");
164 let mut rows = vec![rec(&[("name", "alice")]), rec(&[("name", "zoo")])];
165 key.sort(&mut rows);
166 assert_eq!(rows[0]["name"], "zoo");
167 }
168
169 #[test]
170 fn numeric_asc() {
171 let key = SortKey::new().asc_num("score");
172 let mut rows = vec![
173 rec(&[("score", "10")]),
174 rec(&[("score", "2")]),
175 rec(&[("score", "20")]),
176 ];
177 key.sort(&mut rows);
178 assert_eq!(rows[0]["score"], "2");
179 }
180
181 #[test]
182 fn numeric_desc() {
183 let key = SortKey::new().desc_num("score");
184 let mut rows = vec![rec(&[("score", "5")]), rec(&[("score", "100")])];
185 key.sort(&mut rows);
186 assert_eq!(rows[0]["score"], "100");
187 }
188
189 #[test]
190 fn criterion_count() {
191 let key = SortKey::new().asc("a").desc("b");
192 assert_eq!(key.criterion_count(), 2);
193 }
194
195 #[test]
196 fn empty_key_no_change() {
197 let key = SortKey::new();
198 let mut rows = vec![rec(&[("x", "z")]), rec(&[("x", "a")])];
199 key.sort(&mut rows);
200 assert_eq!(rows[0]["x"], "z"); }
202
203 #[test]
204 fn to_string_repr() {
205 let key = SortKey::new().asc("name").desc("score");
206 assert_eq!(key.to_string_repr(), "name:asc,score:desc");
207 }
208
209 #[test]
210 fn missing_field_treats_as_empty() {
211 let key = SortKey::new().asc("missing");
212 let mut rows = vec![rec(&[("x", "a")]), rec(&[("y", "b")])];
213 key.sort(&mut rows);
214 assert_eq!(rows.len(), 2);
215 }
216
217 #[test]
218 fn clear_criteria() {
219 let mut key = SortKey::new().asc("a");
220 key.clear();
221 assert!(key.is_empty());
222 }
223
224 #[test]
225 fn stable_equal_elements() {
226 let key = SortKey::new().asc("val");
227 let mut rows = vec![rec(&[("val", "same")]), rec(&[("val", "same")])];
228 key.sort(&mut rows);
229 assert_eq!(rows.len(), 2);
230 }
231}