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