1pub mod cuid2;
2pub mod nanoid;
3pub mod snowflake;
4pub mod ulid;
5pub mod uuid_v7;
6
7pub use cuid2::Cuid2;
8pub use nanoid::NanoId;
9pub use snowflake::{Snowflake, SnowflakeConfig};
10pub use ulid::Ulid;
11pub use uuid_v7::UuidV7;
12
13use thiserror::Error;
14
15#[derive(Debug, Error)]
16pub enum IdError {
17 #[error("{0}: {1}")]
18 InvalidFormat(&'static str, &'static str),
19}
20
21#[cfg(test)]
22mod tests {
23 use std::collections::HashSet;
24
25 use super::*;
26
27 #[test]
30 fn cuid2_length() {
31 let id = Cuid2::generate();
32 assert_eq!(id.as_str().len(), 24, "CUID2 must be 24 chars");
33 }
34
35 #[test]
36 fn cuid2_starts_with_letter() {
37 for _ in 0..20 {
38 let id = Cuid2::generate();
39 assert!(
40 id.as_str().chars().next().unwrap().is_ascii_alphabetic(),
41 "CUID2 first char must be a letter"
42 );
43 }
44 }
45
46 #[test]
47 fn cuid2_uniqueness() {
48 let ids: HashSet<String> = (0..1000).map(|_| Cuid2::generate().to_string()).collect();
49 assert_eq!(ids.len(), 1000, "CUID2 collisions detected");
50 }
51
52 #[test]
53 fn cuid2_roundtrip() {
54 let id = Cuid2::generate();
55 let s = id.to_string();
56 let parsed: Cuid2 = s.parse().unwrap();
57 assert_eq!(id, parsed);
58 }
59
60 #[test]
61 fn cuid2_serde() {
62 let id = Cuid2::generate();
63 let json = serde_json::to_string(&id).unwrap();
64 let back: Cuid2 = serde_json::from_str(&json).unwrap();
65 assert_eq!(id, back);
66 }
67
68 #[test]
71 fn ulid_length() {
72 let id = Ulid::generate();
73 assert_eq!(id.as_str().len(), 26, "ULID must be 26 chars");
74 }
75
76 #[test]
77 fn ulid_uniqueness() {
78 let ids: HashSet<String> = (0..1000).map(|_| Ulid::generate().to_string()).collect();
79 assert_eq!(ids.len(), 1000, "ULID collisions detected");
80 }
81
82 #[test]
83 fn ulid_sorted_by_time() {
84 let a = Ulid::generate();
86 std::thread::sleep(std::time::Duration::from_millis(2));
87 let b = Ulid::generate();
88 assert!(
89 a.to_string() < b.to_string(),
90 "ULIDs must be lexicographically ordered"
91 );
92 }
93
94 #[test]
95 fn ulid_timestamp_roundtrip() {
96 let before = std::time::SystemTime::now()
97 .duration_since(std::time::UNIX_EPOCH)
98 .unwrap()
99 .as_millis() as u64;
100 let id = Ulid::generate();
101 let after = std::time::SystemTime::now()
102 .duration_since(std::time::UNIX_EPOCH)
103 .unwrap()
104 .as_millis() as u64;
105 let ts = id.timestamp_ms();
106 assert!(ts >= before && ts <= after, "ULID timestamp out of range");
107 }
108
109 #[test]
110 fn ulid_monotonic_within_ms() {
111 let mut prev = Ulid::monotonic();
112 for _ in 0..50 {
113 let next = Ulid::monotonic();
114 assert!(
115 prev.to_string() < next.to_string(),
116 "monotonic ULIDs must be strictly increasing"
117 );
118 prev = next;
119 }
120 }
121
122 #[test]
123 fn ulid_roundtrip() {
124 let id = Ulid::generate();
125 let s = id.to_string();
126 let parsed: Ulid = s.parse().unwrap();
127 assert_eq!(id, parsed);
128 }
129
130 #[test]
131 fn ulid_serde() {
132 let id = Ulid::generate();
133 let json = serde_json::to_string(&id).unwrap();
134 let back: Ulid = serde_json::from_str(&json).unwrap();
135 assert_eq!(id, back);
136 }
137
138 #[test]
141 fn uuid_v7_version() {
142 let id = UuidV7::generate();
143 assert_eq!(id.as_uuid().get_version_num(), 7);
144 }
145
146 #[test]
147 fn uuid_v7_uniqueness() {
148 let ids: HashSet<String> = (0..1000).map(|_| UuidV7::generate().to_string()).collect();
149 assert_eq!(ids.len(), 1000, "UUID v7 collisions detected");
150 }
151
152 #[test]
153 fn uuid_v7_ordered() {
154 let a = UuidV7::generate();
155 std::thread::sleep(std::time::Duration::from_millis(2));
156 let b = UuidV7::generate();
157 assert!(a.to_string() < b.to_string());
158 }
159
160 #[test]
161 fn uuid_v7_roundtrip() {
162 let id = UuidV7::generate();
163 let s = id.to_string();
164 let parsed: UuidV7 = s.parse().unwrap();
165 assert_eq!(id, parsed);
166 }
167
168 #[test]
171 fn snowflake_positive() {
172 let id = Snowflake::new();
173 assert!(id.value() > 0);
174 }
175
176 #[test]
177 fn snowflake_monotonic() {
178 let cfg = SnowflakeConfig::default();
179 let mut prev = Snowflake::generate(&cfg);
180 for _ in 0..100 {
181 let next = Snowflake::generate(&cfg);
182 assert!(
183 next.value() > prev.value(),
184 "Snowflake IDs must be monotonically increasing"
185 );
186 prev = next;
187 }
188 }
189
190 #[test]
191 fn snowflake_worker_id() {
192 let cfg = SnowflakeConfig {
193 worker_id: 42,
194 epoch_ms: 1_577_836_800_000,
195 };
196 let id = Snowflake::generate(&cfg);
197 assert_eq!(id.worker_id(), 42);
198 }
199
200 #[test]
201 fn snowflake_roundtrip() {
202 let id = Snowflake::new();
203 let s = id.to_string();
204 let parsed: Snowflake = s.parse().unwrap();
205 assert_eq!(id, parsed);
206 }
207
208 #[test]
211 fn nanoid_default_length() {
212 let id = NanoId::generate();
213 assert_eq!(id.as_str().len(), 21);
214 }
215
216 #[test]
217 fn nanoid_custom_size() {
218 let id = NanoId::with_size(36);
219 assert_eq!(id.as_str().len(), 36);
220 }
221
222 #[test]
223 fn nanoid_custom_alphabet() {
224 let alpha = b"0123456789";
225 let id = NanoId::custom(alpha, 10);
226 assert!(id.as_str().chars().all(|c| c.is_ascii_digit()));
227 assert_eq!(id.as_str().len(), 10);
228 }
229
230 #[test]
231 fn nanoid_uniqueness() {
232 let ids: HashSet<String> = (0..1000).map(|_| NanoId::generate().to_string()).collect();
233 assert_eq!(ids.len(), 1000, "NanoID collisions detected");
234 }
235
236 #[test]
237 fn nanoid_serde() {
238 let id = NanoId::generate();
239 let json = serde_json::to_string(&id).unwrap();
240 let back: NanoId = serde_json::from_str(&json).unwrap();
241 assert_eq!(id, back);
242 }
243}