1#![forbid(unsafe_code)]
2use std::collections::HashSet;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct FocusItem {
42 pub id: String,
43 pub order: usize,
44 pub enabled: bool,
45}
46
47#[must_use]
48pub fn sorted_focus_order(items: &[FocusItem]) -> Vec<FocusItem> {
49 let mut sorted = items.to_vec();
50 sorted.sort_by(|left, right| {
51 left.order
52 .cmp(&right.order)
53 .then_with(|| left.id.cmp(&right.id))
54 });
55 sorted
56}
57
58#[must_use]
59pub fn enabled_focus_items(items: &[FocusItem]) -> Vec<FocusItem> {
60 items.iter().filter(|item| item.enabled).cloned().collect()
61}
62
63#[must_use]
64pub fn has_duplicate_focus_order(items: &[FocusItem]) -> bool {
65 let mut seen = HashSet::with_capacity(items.len());
66 items.iter().any(|item| !seen.insert(item.order))
67}
68
69#[must_use]
70pub fn has_empty_focus_id(items: &[FocusItem]) -> bool {
71 items.iter().any(|item| item.id.trim().is_empty())
72}
73
74#[must_use]
75pub fn is_focus_order_valid(items: &[FocusItem]) -> bool {
76 let enabled = enabled_focus_items(items);
77 !has_duplicate_focus_order(&enabled) && !has_empty_focus_id(&enabled)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::{
83 FocusItem, enabled_focus_items, has_duplicate_focus_order, has_empty_focus_id,
84 is_focus_order_valid, sorted_focus_order,
85 };
86
87 #[test]
88 fn sorts_focus_order_and_filters_enabled_items() {
89 let items = [
90 FocusItem {
91 id: String::from("submit"),
92 order: 2,
93 enabled: true,
94 },
95 FocusItem {
96 id: String::from("email"),
97 order: 1,
98 enabled: true,
99 },
100 FocusItem {
101 id: String::from("help"),
102 order: 3,
103 enabled: false,
104 },
105 ];
106
107 let sorted = sorted_focus_order(&items);
108 assert_eq!(sorted[0].id, "email");
109 assert_eq!(enabled_focus_items(&items).len(), 2);
110 assert!(is_focus_order_valid(&items));
111 }
112
113 #[test]
114 fn detects_duplicate_orders_and_empty_ids() {
115 let duplicates = [
116 FocusItem {
117 id: String::from("email"),
118 order: 1,
119 enabled: true,
120 },
121 FocusItem {
122 id: String::from("submit"),
123 order: 1,
124 enabled: true,
125 },
126 ];
127 let empty = [FocusItem {
128 id: String::from(" "),
129 order: 1,
130 enabled: true,
131 }];
132
133 assert!(has_duplicate_focus_order(&duplicates));
134 assert!(!is_focus_order_valid(&duplicates));
135 assert!(has_empty_focus_id(&empty));
136 assert!(!is_focus_order_valid(&empty));
137 }
138}