1pub fn reaction_type_key(sub_type: &str) -> Option<char> {
15 match sub_type {
16 "LIKE" => Some('L'),
17 "LOVE" => Some('V'),
18 "LAUGH" => Some('H'),
19 "WOW" => Some('W'),
20 "SAD" => Some('S'),
21 "ANGRY" => Some('A'),
22 _ => None,
23 }
24}
25
26pub fn encode_reaction_counts(mut entries: Vec<(char, u32)>, total: u32) -> String {
30 if total == 0 {
31 return String::new();
32 }
33 entries.retain(|(_, c)| *c > 0);
34 entries.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
35 entries.truncate(5);
36 if entries.is_empty() {
37 return total.to_string();
38 }
39 let mut out = total.to_string();
40 for (k, c) in &entries {
41 out.push(',');
42 out.push(*k);
43 out.push_str(&c.to_string());
44 }
45 out
46}
47
48pub fn decode_reaction_counts(s: &str) -> (Vec<(char, u32)>, u32) {
51 if s.is_empty() {
52 return (Vec::new(), 0);
53 }
54 let mut parts = s.split(',');
55 let total: u32 = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0);
56 let mut entries = Vec::new();
57 for part in parts {
58 let mut chars = part.chars();
59 let Some(key) = chars.next() else { continue };
60 let n_str: String = chars.collect();
61 let Ok(n) = n_str.parse::<u32>() else { continue };
62 if n == 0 {
63 continue;
64 }
65 entries.push((key, n));
66 }
67 (entries, total)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn encode_empty() {
76 assert_eq!(encode_reaction_counts(Vec::new(), 0), "");
77 }
78
79 #[test]
80 fn encode_single() {
81 assert_eq!(encode_reaction_counts(vec![('L', 1)], 1), "1,L1");
82 }
83
84 #[test]
85 fn encode_with_overflow() {
86 assert_eq!(
88 encode_reaction_counts(vec![('L', 40), ('V', 30), ('H', 20), ('W', 7), ('S', 5)], 103,),
89 "103,L40,V30,H20,W7,S5"
90 );
91 }
92
93 #[test]
94 fn encode_total_only() {
95 assert_eq!(encode_reaction_counts(Vec::new(), 7), "7");
97 }
98
99 #[test]
100 fn encode_normalises_unsorted_input() {
101 let s = encode_reaction_counts(vec![('A', 1), ('L', 5), ('V', 3)], 9);
103 assert_eq!(s, "9,L5,V3,A1");
104 }
105
106 #[test]
107 fn encode_caps_to_top_five() {
108 let s = encode_reaction_counts(
110 vec![('A', 6), ('B', 5), ('C', 4), ('D', 3), ('E', 2), ('F', 1)],
111 21,
112 );
113 assert_eq!(s, "21,A6,B5,C4,D3,E2");
114 }
115
116 #[test]
117 fn decode_empty() {
118 assert_eq!(decode_reaction_counts(""), (Vec::new(), 0));
119 }
120
121 #[test]
122 fn decode_total_only() {
123 assert_eq!(decode_reaction_counts("7"), (Vec::new(), 7));
124 }
125
126 #[test]
127 fn decode_with_entries() {
128 assert_eq!(
129 decode_reaction_counts("103,L40,V30,H20,W7,S5"),
130 (vec![('L', 40), ('V', 30), ('H', 20), ('W', 7), ('S', 5)], 103)
131 );
132 }
133
134 #[test]
135 fn roundtrip_encode_decode_encode() {
136 let original = "103,L40,V30,H20,W7,S5";
137 let (entries, total) = decode_reaction_counts(original);
138 assert_eq!(encode_reaction_counts(entries, total), original);
139 }
140
141 #[test]
142 fn decode_skips_malformed() {
143 let (entries, total) = decode_reaction_counts("5,L3,xy,V2");
145 assert_eq!(total, 5);
146 assert_eq!(entries, vec![('L', 3), ('V', 2)]);
147 }
148}
149
150