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 #[cfg(feature = "serde")]
123 #[test]
124 fn test_serde_roundtrip() {
125 let id = UploadId::try_from("wcfytxww4opin4jmjjes4ccfd".to_string())
126 .expect("Failed to decode UploadId");
127 let serialized = serde_json::to_string(&id).expect("Failed to serialize UploadId");
128 let deserialized: UploadId =
129 serde_json::from_str(&serialized).expect("Failed to deserialize UploadId");
130 assert_eq!(id, deserialized);
131 }
132
133 #[test]
134 fn test_too_short_error() {
135 let invalid_id = String::from("aa");
136 let result = UploadId::try_from(invalid_id);
137 assert!(result.is_err());
138 let err = result.expect_err("Should fail due to invalid check-bit");
139 assert_eq!(err.to_string(), "ID length too short, minimum 3 characters");
140 }
141
142 #[test]
143 fn test_weird_unicode() {
144 let invalid_id = String::from("🦀🦀🦀");
145 let result = UploadId::try_from(invalid_id);
146 assert!(result.is_err());
147 let err = result.expect_err("Should fail due to invalid characters");
148 assert_eq!(err.to_string(), "Invalid character in ID");
149 }
150
151 #[test]
152 fn test_invalid_chars() {
153 let invalid_id = String::from("¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿gg");
154 let result = UploadId::try_from(invalid_id);
155 assert!(result.is_err());
156 let err = result.expect_err("Should fail due to invalid characters");
157 assert_eq!(err.to_string(), "Invalid character in ID");
158 }
159}