1use std::collections::HashMap;
30
31pub struct SquishyID {
33 length: usize,
34 characters_to_positions: HashMap<char, usize>,
35 positions_to_characters: Vec<char>,
36}
37
38impl SquishyID {
39 pub fn new(key: &str) -> Result<Self, &str> {
57 let positions_to_characters: Vec<char> = key.chars().collect();
58
59 let length: usize = positions_to_characters.len();
60 if length < 2 {
61 return Err("Key must contain at least 2 characters.");
62 }
63
64 let mut characters_to_positions: HashMap<char, usize> = HashMap::new();
65 for (position, &character) in positions_to_characters.iter().enumerate() {
66 if characters_to_positions
67 .insert(character, position)
68 .is_some()
69 {
70 return Err("Key must contain unique characters.");
71 }
72 }
73
74 Ok(Self {
75 length,
76 characters_to_positions,
77 positions_to_characters,
78 })
79 }
80
81 pub fn encode(&self, mut decoded: u64) -> String {
88 let mut encoded: Vec<char> = Vec::new();
89
90 loop {
91 let position: u64 = decoded % (self.length as u64);
92 encoded.push(self.positions_to_characters[position as usize]);
93 decoded /= self.length as u64;
94
95 if decoded == 0 {
96 break;
97 }
98 }
99
100 encoded.iter().rev().collect()
101 }
102
103 pub fn decode(&self, encoded: &str) -> Result<u64, &str> {
110 if encoded.is_empty() {
111 return Err("Encoded value must contain at least 1 character.");
112 }
113
114 let mut decoded: u64 = 0;
115
116 for (position, character) in encoded.chars().rev().enumerate() {
117 let factor: u64 = match self.characters_to_positions.get(&character) {
118 None => return Err("Encoded value contains character not present in key."),
119 Some(&factor) => factor as u64,
120 };
121
122 match (self.length as u64)
123 .checked_pow(position as u32)
124 .and_then(|a| a.checked_mul(factor))
125 .and_then(|a| a.checked_add(decoded))
126 {
127 None => return Err("Encoded value too big to decode."),
128 Some(bigger_decoded) => decoded = bigger_decoded,
129 }
130 }
131
132 Ok(decoded)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn key_too_short() {
142 assert!(matches!(
143 SquishyID::new(""),
144 Err("Key must contain at least 2 characters.")
145 ));
146 assert!(matches!(
147 SquishyID::new("a"),
148 Err("Key must contain at least 2 characters.")
149 ));
150 }
151
152 #[test]
153 fn key_not_unique() {
154 assert!(matches!(
155 SquishyID::new("aa"),
156 Err("Key must contain unique characters.")
157 ));
158 assert!(matches!(
159 SquishyID::new("aba"),
160 Err("Key must contain unique characters.")
161 ));
162 }
163
164 #[test]
165 fn key_valid() {
166 assert!(SquishyID::new("ab").is_ok());
167 }
168
169 #[test]
170 fn transcode_0_value() {
171 let s = SquishyID::new("ab").unwrap();
172 assert_eq!(s.encode(0), "a");
173 assert_eq!(s.decode("a").unwrap(), 0);
174 }
175
176 #[test]
177 fn transcode_u64_value() {
178 let s = SquishyID::new("FujSBZHkPMincNQr6pq0mgxw2tXAsyb8DWV534EC1RUIlYoGOJhed9afKT7vzL")
179 .unwrap();
180 assert_eq!(s.encode(u64::MAX), "gzUp3uHipVr");
181 assert_eq!(s.decode("gzUp3uHipVr").unwrap(), u64::MAX);
182 }
183
184 #[test]
185 fn transcode_non_ascii() {
186 let s = SquishyID::new("รคฤ
").unwrap();
187 assert_eq!(s.encode(8), "ฤ
รครครค");
188 assert_eq!(s.decode("ฤ
รครครค").unwrap(), 8);
189
190 let s = SquishyID::new("๐๐๐๐๐๐
๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ด๐ต๐ถ๐ท").unwrap();
191 assert_eq!(s.encode(48888851145), "๐๐ ๐ซ๐๐ต๐๐");
192 assert_eq!(s.decode("๐๐ ๐ซ๐๐ต๐๐").unwrap(), 48888851145);
193 }
194
195 #[test]
196 fn decode_empty_string() {
197 let s = SquishyID::new("ab").unwrap();
198 assert!(matches!(
199 s.decode(""),
200 Err("Encoded value must contain at least 1 character.")
201 ));
202 }
203
204 #[test]
205 fn decode_character_not_in_key() {
206 let s = SquishyID::new("ab").unwrap();
207 assert!(matches!(
208 s.decode("x"),
209 Err("Encoded value contains character not present in key.")
210 ));
211 }
212
213 #[test]
214 fn decode_overflow() {
215 let s = SquishyID::new("0123456789ABCDEF").unwrap();
216 assert!(matches!(
217 s.decode("10000000000000000"),
218 Err("Encoded value too big to decode.")
219 ));
220 }
221}