Skip to main content

banana/
lib.rs

1use rand::Rng;
2use std::{cmp, fmt};
3use wasm_bindgen::prelude::*;
4
5pub const ALPHABETS: [&'static str; 2] = ["bcdfglmnprstvz", "aeiou"];
6
7#[derive(Debug, PartialEq)]
8pub enum BananaError {
9    InvalidBanana,
10    InvalidAlphabet,
11}
12
13impl fmt::Display for BananaError {
14    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15        match self {
16            BananaError::InvalidBanana => write!(f, "Invalid banana number"),
17            BananaError::InvalidAlphabet => write!(f, "Invalid alphabet"),
18        }
19    }
20}
21
22fn valid_alphabet(alphabet: &Vec<char>) -> bool {
23    for (i, c) in alphabet.iter().enumerate() {
24        if alphabet.iter().rposition(|v| c == v).unwrap() != i {
25            return false;
26        }
27    }
28
29    true
30}
31
32fn get_alphabets(alphabets: Option<&[String]>) -> Result<Vec<Vec<char>>, BananaError> {
33    let result = alphabets
34        .as_deref()
35        .map(|a| a.iter().map(|s| &**s).collect::<Vec<&str>>())
36        .unwrap_or(ALPHABETS.to_vec())
37        .iter()
38        .map(|alphabet| alphabet.chars().collect())
39        .collect();
40
41    for alphabet in &result {
42        if !valid_alphabet(alphabet) {
43            return Err(BananaError::InvalidAlphabet);
44        }
45    }
46
47    Ok(result)
48}
49
50#[derive(Default, Debug)]
51pub struct EncodeParams {
52    pub alphabet_shift: Option<usize>,
53    pub alphabet_end: Option<usize>,
54    pub min_length: Option<usize>,
55    pub alphabets: Option<Vec<String>>,
56}
57
58pub fn encode(num: u64, params: &EncodeParams) -> Result<String, BananaError> {
59    let alphabets = get_alphabets(params.alphabets.as_deref())?;
60
61    let n_alphabets = alphabets.len();
62    let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
63    let alphabet_end = params.alphabet_end.unwrap_or(0) % n_alphabets;
64    let min_length = cmp::max(1, params.min_length.unwrap_or(1));
65
66    let mut v = num;
67    let mut word = Vec::new();
68
69    let mut idx = (n_alphabets - 1 + alphabet_shift + alphabet_end) % n_alphabets;
70    while v != 0
71        || (idx != (n_alphabets - 1 + alphabet_shift) % n_alphabets)
72        || word.len() < min_length
73    {
74        let alphabet_len = alphabets[idx].len() as u64;
75        if alphabet_len == 0 {
76            return Err(BananaError::InvalidAlphabet);
77        }
78
79        let r: usize = usize::try_from(v % alphabet_len).unwrap();
80        v = v / alphabet_len;
81
82        word.push(alphabets[idx][r]);
83        idx = (idx + n_alphabets - 1) % n_alphabets;
84    }
85
86    word.reverse();
87    Ok(word.iter().collect())
88}
89
90pub fn random(params: &EncodeParams) -> Result<String, BananaError> {
91    let alphabets = get_alphabets(params.alphabets.as_deref())?;
92
93    let n_alphabets = alphabets.len();
94
95    let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
96    let alphabet_end = params.alphabet_end.unwrap_or(0);
97    let min_length = cmp::max(1, params.min_length.unwrap_or(1));
98
99    if min_length == 0 {
100        return Ok("".to_string());
101    }
102
103    let mut word = Vec::new();
104    let mut rng = rand::thread_rng();
105
106    let mut idx = (n_alphabets - 1 + alphabet_shift + alphabet_end) % n_alphabets;
107
108    while idx != (n_alphabets - 1 + alphabet_shift) % n_alphabets || word.len() < min_length {
109        let alphabet_len = alphabets[idx].len();
110        if alphabet_len == 0 {
111            return Err(BananaError::InvalidAlphabet);
112        }
113        let r = rng.gen_range(0..alphabet_len);
114        word.push(alphabets[idx][r]);
115        idx = (idx + n_alphabets - 1) % n_alphabets;
116    }
117
118    word.reverse();
119    Ok(word.iter().collect())
120}
121
122#[derive(Default, Debug)]
123pub struct DecodeParams {
124    pub alphabet_shift: Option<usize>,
125    pub alphabet_end: Option<usize>,
126    pub alphabets: Option<Vec<String>>,
127}
128
129pub fn decode(word: &str, params: &DecodeParams) -> Result<u64, BananaError> {
130    let alphabets = get_alphabets(params.alphabets.as_deref())?;
131
132    let n_alphabets = alphabets.len();
133
134    let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
135    let alphabet_end = params.alphabet_end.unwrap_or(0) % n_alphabets;
136
137    if (word.chars().count() - alphabet_end) % n_alphabets != 0 {
138        return Err(BananaError::InvalidBanana);
139    }
140
141    let mut v: u64 = 0;
142    for (i, c) in word.chars().enumerate() {
143        let idx = (n_alphabets + i + alphabet_shift) % n_alphabets;
144        let alphabet = &alphabets[idx];
145        match alphabet.iter().position(|&x| x == c) {
146            Some(pos) => v = v * alphabet.len() as u64 + pos as u64,
147            None => return Err(BananaError::InvalidBanana),
148        };
149    }
150
151    Ok(v)
152}
153
154pub fn is_valid(word: &str, params: &DecodeParams) -> Result<bool, BananaError> {
155    let alphabets = get_alphabets(params.alphabets.as_deref())?;
156
157    let n_alphabets = alphabets.len();
158
159    let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
160    let alphabet_end = params.alphabet_end.unwrap_or(0) % n_alphabets;
161
162    if (word.chars().count() - alphabet_end) % n_alphabets != 0 {
163        return Ok(false);
164    }
165
166    for (i, c) in word.chars().enumerate() {
167        let idx = (n_alphabets + i + alphabet_shift) % n_alphabets;
168        let alphabet = &alphabets[idx];
169        if alphabet.iter().position(|&x| x == c).is_none() {
170            return Ok(false);
171        }
172    }
173
174    return Ok(true);
175}
176
177#[macro_export]
178macro_rules! encode {
179    ($a: expr) => {
180        encode($a, &EncodeParams::default())
181    };
182}
183
184#[macro_export]
185macro_rules! decode {
186    ($a: expr) => {
187        decode($a, &DecodeParams::default())
188    };
189}
190
191#[macro_export]
192macro_rules! random {
193    () => {
194        random(&EncodeParams::default())
195    };
196}
197
198#[macro_export]
199macro_rules! is_valid {
200    ($a: expr) => {
201        is_valid($a, &DecodeParams::default())
202    };
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_macro() {
211        for test in [
212            (1, "be"),
213            (1000, "duga"),
214            (100000000000, "tumuzadofopa"),
215            (u64::MAX, "cenicovutinazamofevafa"),
216        ] {
217            assert_eq!(encode!(test.0).unwrap(), test.1);
218            assert_eq!(decode!(test.1).unwrap(), test.0);
219            assert_eq!(is_valid!(&encode!(test.0).unwrap()).unwrap(), true);
220        }
221
222        assert_eq!(is_valid!("123").unwrap(), false);
223        assert!(random!().unwrap().len() > 0)
224    }
225
226    #[test]
227    fn test_alphabet_invalid() {
228        assert_eq!(
229            encode(
230                1,
231                &EncodeParams {
232                    alphabets: Some(vec!["abc".to_string(), "123".to_string()]),
233                    ..Default::default()
234                }
235            )
236            .unwrap(),
237            "a2"
238        );
239        assert_eq!(
240            encode(
241                1,
242                &EncodeParams {
243                    alphabets: Some(vec!["aba".to_string(), "123".to_string()]),
244                    ..Default::default()
245                }
246            )
247            .err()
248            .unwrap(),
249            BananaError::InvalidAlphabet
250        );
251        assert_eq!(
252            encode(
253                1,
254                &EncodeParams {
255                    alphabets: Some(vec!["abcdefak".to_string(), "123".to_string()]),
256                    ..Default::default()
257                }
258            )
259            .err()
260            .unwrap(),
261            BananaError::InvalidAlphabet
262        );
263        assert_eq!(
264            encode(
265                1,
266                &EncodeParams {
267                    alphabets: Some(vec!["a".to_string(), "1232789".to_string()]),
268                    ..Default::default()
269                }
270            )
271            .err()
272            .unwrap(),
273            BananaError::InvalidAlphabet
274        );
275    }
276
277    #[test]
278    fn test_alphabet_shift() {
279        for test in [
280            (1, "be", 0),
281            (1, "ac", 1),
282            (1000, "ebem", 3),
283            (1, "ac", usize::MAX),
284        ] {
285            assert_eq!(
286                encode(
287                    test.0,
288                    &EncodeParams {
289                        alphabet_shift: Some(test.2),
290                        ..Default::default()
291                    },
292                )
293                .unwrap(),
294                test.1
295            );
296
297            assert_eq!(
298                decode(
299                    test.1,
300                    &DecodeParams {
301                        alphabet_shift: Some(test.2),
302                        ..Default::default()
303                    },
304                )
305                .unwrap(),
306                test.0
307            );
308        }
309    }
310
311    #[test]
312    fn test_alphabet_end() {
313        for test in [
314            (1, "be", 0),
315            (1, "c", 1),
316            (1000, "bebem", 3),
317            (1, "c", usize::MAX),
318        ] {
319            assert_eq!(
320                encode(
321                    test.0,
322                    &EncodeParams {
323                        alphabet_end: Some(test.2),
324                        ..Default::default()
325                    },
326                )
327                .unwrap(),
328                test.1
329            );
330
331            assert_eq!(
332                decode(
333                    test.1,
334                    &DecodeParams {
335                        alphabet_end: Some(test.2),
336                        ..Default::default()
337                    },
338                )
339                .unwrap(),
340                test.0
341            );
342        }
343    }
344
345    #[test]
346    fn test_min_length() {
347        for test in [
348            (1, "be", 0),
349            (1, "be", 1),
350            (1, "bababababe", 10),
351            (1000, "bababaduga", 10),
352        ] {
353            assert_eq!(
354                encode(
355                    test.0,
356                    &EncodeParams {
357                        min_length: Some(test.2),
358                        ..Default::default()
359                    },
360                )
361                .unwrap(),
362                test.1
363            );
364
365            assert_eq!(decode!(test.1).unwrap(), test.0);
366        }
367    }
368
369    #[test]
370    fn test_alphabets() {
371        for test in [
372            (1, "aw", vec!["abc".to_string(), "qwe".to_string()]),
373            (8, "ce", vec!["abc".to_string(), "qwe".to_string()]),
374            (9, "awaq", vec!["abc".to_string(), "qwe".to_string()]),
375            (
376                9,
377                "bq1",
378                vec!["abc".to_string(), "qwe".to_string(), "123".to_string()],
379            ),
380            (
381                27,
382                "aq2aq1",
383                vec!["abc".to_string(), "qwe".to_string(), "123".to_string()],
384            ),
385            (
386                0,
387                "🐼🐢🐱",
388                vec![
389                    "🐼🐡🦍".to_string(),
390                    "🐢🐺🦊".to_string(),
391                    "🐱🦁🐯".to_string(),
392                ],
393            ),
394            (
395                27,
396                "🐼🐢🦁🐼🐢🐱",
397                vec![
398                    "🐼🐡🦍".to_string(),
399                    "🐢🐺🦊".to_string(),
400                    "🐱🦁🐯".to_string(),
401                ],
402            ),
403        ] {
404            assert_eq!(
405                encode(
406                    test.0,
407                    &EncodeParams {
408                        alphabets: Some(test.2.clone()),
409                        ..Default::default()
410                    },
411                )
412                .unwrap(),
413                test.1
414            );
415
416            assert_eq!(
417                decode(
418                    test.1,
419                    &DecodeParams {
420                        alphabets: Some(test.2.clone()),
421                        ..Default::default()
422                    }
423                )
424                .unwrap(),
425                test.0
426            );
427        }
428    }
429}
430
431#[wasm_bindgen]
432pub fn banana_encode(
433    num: u64,
434    alphabet_shift: Option<usize>,
435    alphabet_end: Option<usize>,
436    min_length: Option<usize>,
437    alphabets: Option<Vec<String>>,
438) -> String {
439    encode(
440        num,
441        &EncodeParams {
442            alphabet_shift: alphabet_shift,
443            alphabet_end: alphabet_end,
444            min_length: min_length,
445            alphabets: alphabets,
446        },
447    )
448    .unwrap_or("".to_string())
449}
450
451#[wasm_bindgen]
452pub fn banana_random(
453    alphabet_shift: Option<usize>,
454    alphabet_end: Option<usize>,
455    min_length: Option<usize>,
456    alphabets: Option<Vec<String>>,
457) -> String {
458    random(&EncodeParams {
459        alphabet_shift: alphabet_shift,
460        alphabet_end: alphabet_end,
461        min_length: min_length,
462        alphabets: alphabets,
463    })
464    .unwrap_or("".to_string())
465}
466
467#[wasm_bindgen]
468pub fn banana_decode(
469    word: &str,
470    alphabet_shift: Option<usize>,
471    alphabet_end: Option<usize>,
472    alphabets: Option<Vec<String>>,
473) -> u64 {
474    decode(
475        word,
476        &DecodeParams {
477            alphabet_shift: alphabet_shift,
478            alphabet_end: alphabet_end,
479            alphabets: alphabets,
480        },
481    )
482    .unwrap_or(0)
483}
484
485#[wasm_bindgen]
486pub fn banana_is_valid(
487    word: &str,
488    alphabet_shift: Option<usize>,
489    alphabet_end: Option<usize>,
490    alphabets: Option<Vec<String>>,
491) -> bool {
492    is_valid(
493        word,
494        &DecodeParams {
495            alphabet_shift: alphabet_shift,
496            alphabet_end: alphabet_end,
497            alphabets: alphabets,
498        },
499    )
500    .unwrap_or(false)
501}