human_friendly_ids/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(clippy::all, clippy::pedantic)]
3#![allow(clippy::uninlined_format_args)]
4
5pub mod alphabet;
6pub mod error;
7pub mod id;
8
9pub use crate::id::Id;
10
11#[allow(
12    clippy::all,
13    clippy::pedantic,
14    unused_must_use,
15    reason = "It's a test, bro."
16)]
17#[cfg(test)]
18mod tests {
19    use std::convert::TryFrom;
20
21    use rand::Rng;
22
23    use super::*;
24    use crate::alphabet::GEN_ALPHABET;
25
26    #[test]
27    fn assert_largest_id_is_fixed() {
28        let largest = Id::max_length();
29        assert_eq!(largest, 838_488_366_986_797_801); // Absurdly large number, but it's fixed.
30
31        // Try and generate an id with a very large length, notably this will allocate a string
32        // of this size.
33        const TEST_SIZE: usize = 1024 * 1024; // 1mb
34
35        let id = Id::new(TEST_SIZE);
36        assert_eq!(id.as_str().len(), TEST_SIZE);
37
38        // Decode and re-encode the id.
39        let id_str = id.to_string();
40        let id_decoded: Id = id_str.parse().expect("Failed to decode UploadId");
41
42        assert_eq!(id_decoded.to_string(), id_str);
43    }
44
45    #[test]
46    fn test_decode() {
47        let test_string = String::from("wcfytxww4opin4jmjjes4ccfd");
48        let decoded = Id::try_from(test_string).expect("Failed to decode UploadId");
49        assert_eq!(
50            decoded.as_str(),
51            "wcfytxww4opin4jmjjes4ccfd",
52            "decoded value should be equal to input string"
53        );
54    }
55
56    #[test]
57    fn fuzz_generated_ids() {
58        for _ in 0_u64..10_000_u64 {
59            let id = Id::new(25);
60            println!("{}", id);
61            assert_eq!(id.as_str().len(), 25);
62
63            // Assert that serializing and deserializing the id doesn't change it.
64            let id_str = id.to_string();
65            let id = Id::try_from(id_str.clone()).expect("Failed to decode UploadId");
66            assert_eq!(id.to_string(), id_str);
67        }
68    }
69
70    #[test]
71    fn fuzz_gen_alphabet_strings() {
72        let mut rng = rand::rng();
73        for _ in 0..100_000_u64 {
74            // Generate a random string of characters from 2 to 25 characters long.
75            let string = (0..rng.random_range(2..25))
76                .map(|_| GEN_ALPHABET[rng.random_range(0..GEN_ALPHABET.len())])
77                .collect::<String>();
78
79            // Try and decode it - should not panic.
80            Id::try_from(string.clone());
81        }
82    }
83
84    #[test]
85    fn fuzz_random_strings() {
86        let mut rng = rand::rng();
87        for _ in 0..100_000_u64 {
88            // Generate a random string of characters from 2 to 25 characters long.
89            let string = (0..rng.random_range(2..25))
90                .map(|_| rng.random_range(0..=255) as u8 as char)
91                .collect::<String>();
92
93            // Try and decode it - should not panic.
94            Id::try_from(string.clone());
95        }
96    }
97
98    #[test]
99    fn test_invalid_chars_error() {
100        let id = "abc123".to_string();
101        let result = Id::try_from(id);
102        assert!(result.is_err());
103        let err = result.expect_err("Should fail due to invalid characters");
104        assert_eq!(err.to_string(), "Invalid check bit");
105    }
106
107    #[test]
108    fn test_invalid_check_bit_error() {
109        let invalid_id = String::from("abbsyhbbb4tyxnnmrtjx4crom");
110        let result = Id::try_from(invalid_id);
111        assert!(result.is_err());
112        let err = result.expect_err("Should fail due to invalid check-bit");
113        assert_eq!(err.to_string(), "Invalid check bit");
114    }
115
116    #[test]
117    fn test_too_short_error() {
118        let invalid_id = String::from("aa");
119        let result = Id::try_from(invalid_id);
120        assert!(result.is_err());
121        let err = result.expect_err("Should fail due to invalid check-bit");
122        assert_eq!(err.to_string(), "ID length too short, minimum 3 characters");
123    }
124
125    #[test]
126    fn test_weird_unicode() {
127        let invalid_id = String::from("🦀🦀🦀");
128        let result = Id::try_from(invalid_id);
129        assert!(result.is_err());
130        let err = result.expect_err("Should fail due to invalid characters");
131        assert_eq!(err.to_string(), "Invalid character in ID");
132    }
133
134    #[test]
135    fn test_invalid_chars() {
136        let invalid_id = String::from("¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿gg");
137        let result = Id::try_from(invalid_id);
138        assert!(result.is_err());
139        let err = result.expect_err("Should fail due to invalid characters");
140        assert_eq!(err.to_string(), "Invalid character in ID");
141    }
142}