alphaid/lib.rs
1//! Generate Youtube-Like IDs with Rust.
2//!
3//! ## Basic Usage
4//!
5//! ```rust
6//! use alphaid::AlphaId;
7//!
8//! let alphaid = AlphaId::<u32>::new();
9//! assert_eq!(alphaid.encode(1350997667), Ok(b"90F7qb".to_vec()));
10//! assert_eq!(alphaid.decode(b"90F7qb"), Ok(1350997667));
11//! ```
12//!
13//! ## Padding
14//! Specifies the minimum length of the encoded result.
15//!
16//! ```rust
17//! use alphaid::AlphaId;
18//!
19//! let alphaid = AlphaId::<u32>::new();
20//! assert_eq!(alphaid.encode(0), Ok(b"a".to_vec()));
21//! assert_eq!(alphaid.decode(b"a"), Ok(0));
22//!
23//!
24//! let alphaid = AlphaId::<u128>::builder().pad(5).build();
25//! assert_eq!(alphaid.encode(0), Ok(b"aaaab".to_vec()));
26//! assert_eq!(alphaid.decode(b"aaaab"), Ok(0));
27//! ```
28//!
29//! ## Charaters set
30//! Sets the characters set. Default to `abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_`
31//!
32//! ```rust
33//! use alphaid::AlphaId;
34//! let alphaid = AlphaId::<u32>::builder().pad(2)
35//! .chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ".as_bytes().to_vec())
36//! .build();
37//! assert_eq!(alphaid.encode(0), Ok(b"AB".to_vec()));
38//! assert_eq!(alphaid.decode(b"AB"), Ok(0));
39//! ```
40//!
41//!
42//! ## Reference
43//!
44//! [Create Youtube-Like IDs](https://kvz.io/create-short-ids-with-php-like-youtube-or-tinyurl.html)
45use num::{Bounded, FromPrimitive, Integer, NumCast, ToPrimitive};
46use std::collections::HashMap;
47use std::marker::PhantomData;
48
49pub trait UnsignedInteger:
50 Integer + Bounded + ToPrimitive + FromPrimitive + NumCast + Copy
51{
52}
53
54impl UnsignedInteger for u16 {}
55impl UnsignedInteger for u32 {}
56impl UnsignedInteger for u64 {}
57impl UnsignedInteger for usize {}
58impl UnsignedInteger for u128 {}
59
60static DEFAULT_SEED: &'static str =
61 "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
62
63#[derive(Debug, PartialEq)]
64pub enum AlphaIdError {
65 InvalidNumber,
66 PadMissed,
67 Overflow,
68 UnexpectedChar,
69}
70
71/// A builder for a `AlphaId`.
72pub struct Builder<T: UnsignedInteger = u128> {
73 chars: Option<Vec<u8>>,
74 pad: Option<u32>,
75 _data: PhantomData<T>,
76}
77
78impl<T: UnsignedInteger> Default for Builder<T> {
79 fn default() -> Self {
80 Self {
81 chars: None,
82 pad: None,
83 _data: PhantomData,
84 }
85 }
86}
87
88impl<T: UnsignedInteger> Builder<T> {
89 /// Constructs a new `Builder`.
90 ///
91 /// Parameters are initialized with their default values.
92 pub fn new() -> Self {
93 Default::default()
94 }
95
96 /// Sets the characters set.
97 ///
98 /// Default to `abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_`.
99 ///
100 /// # Panics
101 ///
102 /// Panics if chars' size is less than `16`.
103 pub fn chars(mut self, chars: Vec<u8>) -> Self {
104 assert!(chars.len() > 16, "chars size must large than 16");
105 self.chars = Some(chars);
106 self
107 }
108
109 /// Sets the pad which specifies the minimum
110 /// length of the encoded result.
111 ///
112 /// Default to 1.
113 ///
114 /// # Panics
115 ///
116 /// Panics if pad is less than 0.
117 pub fn pad(mut self, pad: u32) -> Self {
118 assert!(pad > 0, "pad must large than 1");
119 self.pad = Some(pad);
120 self
121 }
122
123 /// Consumes the builder, returning a `AlphaId`.
124 ///
125 /// # Panics
126 ///
127 /// Panics if there are duplicate characters in chars.
128 pub fn build(self) -> AlphaId<T> {
129 let chars = self
130 .chars
131 .unwrap_or_else(|| DEFAULT_SEED.as_bytes().to_vec());
132
133 let index: HashMap<u8, T> = chars
134 .iter()
135 .enumerate()
136 .map(|(i, v)| (*v, T::from_usize(i).unwrap()))
137 .collect();
138
139 assert!(
140 chars.len() == index.len(),
141 "duplicate characters are not allowed"
142 );
143 let base = T::from_usize(chars.len()).expect("primitive number types");
144 let a: f64 = <f64 as NumCast>::from(T::max_value()).unwrap();
145 let b: f64 = <f64 as NumCast>::from(base.clone()).unwrap();
146 let max_pow_i = a.log(b) as u32;
147 AlphaId {
148 chars,
149 index,
150 base,
151 pad: self.pad.unwrap_or(1),
152 max_pow_i,
153 }
154 }
155}
156
157/// Used for encoding and decoding.
158pub struct AlphaId<T: UnsignedInteger = u128> {
159 chars: Vec<u8>,
160 index: HashMap<u8, T>,
161 base: T,
162 pad: u32,
163 max_pow_i: u32,
164}
165
166impl<T: UnsignedInteger> AlphaId<T> {
167 /// Returns a builder type to configure a new `AlphaId`.
168 pub fn builder() -> Builder<T> {
169 Builder::new()
170 }
171
172 /// Creates a new `AlphaId` with a default configuration.
173 pub fn new() -> Self {
174 Builder::new().build()
175 }
176
177 /// Encode the numbers.
178 ///
179 /// # Example
180 ///
181 /// ```rust
182 /// use alphaid::AlphaId;
183 ///
184 /// let alphaid = AlphaId::<u32>::new();
185 /// assert_eq!(alphaid.encode(0), Ok(b"a".to_vec()));
186 /// assert_eq!(alphaid.encode(1), Ok(b"b".to_vec()));
187 /// assert_eq!(alphaid.encode(1350997667), Ok(b"90F7qb".to_vec()));
188 /// ```
189 pub fn encode(&self, mut n: T) -> Result<Vec<u8>, AlphaIdError> {
190 let mut out = vec![];
191 let mut i = 0;
192 loop {
193 i += 1;
194 if self.pad > 1 && self.pad == i {
195 n = n + T::one();
196 }
197
198 if n.is_zero() {
199 if i <= self.pad {
200 out.push(self.chars[0]);
201 continue;
202 }
203 break;
204 }
205
206 let a = n % self.base;
207 out.push(self.chars[a.to_usize().ok_or_else(|| AlphaIdError::InvalidNumber)?]);
208 n = n / self.base;
209 }
210
211 Ok(out)
212 }
213
214 /// Decode into numbers.
215 ///
216 /// # Example
217 ///
218 /// ```rust
219 /// use alphaid::AlphaId;
220 ///
221 /// let alphaid = AlphaId::<u32>::new();
222 /// assert_eq!(alphaid.decode(b"a"), Ok(0));
223 /// assert_eq!(alphaid.decode(b"b"), Ok(1));
224 /// assert_eq!(alphaid.decode(b"90F7qb"), Ok(1350997667));
225 ///```
226 pub fn decode<V: AsRef<[u8]>>(&self, v: V) -> Result<T, AlphaIdError> {
227 let v = v.as_ref();
228 let mut i: usize = 0;
229 let mut n = T::zero();
230 let mut unpad = self.pad > 1;
231 let mut prev = T::zero();
232
233 while i < v.len() {
234 match self.index.get(&v[i as usize]) {
235 Some(t) => {
236 let mut x = *t;
237
238 if unpad {
239 if x.is_zero() && i + 1 == v.len() {
240 return Err(AlphaIdError::PadMissed);
241 }
242
243 if i + 1 >= self.pad as usize {
244 if i > 1 && i + 1 > self.pad as usize {
245 n = n + num::pow(self.base, i - 1) * (self.base - prev - T::one());
246 }
247
248 if !x.is_zero() {
249 unpad = false;
250 x = x - T::one();
251 }
252 }
253 };
254
255 prev = *t;
256
257 if x.is_zero() {
258 i += 1;
259 continue;
260 }
261
262 if i > self.max_pow_i as usize {
263 return Err(AlphaIdError::Overflow);
264 }
265
266 let pow = num::pow(self.base, i);
267 if T::max_value().div(pow) < x {
268 return Err(AlphaIdError::Overflow);
269 }
270 let add = pow * x;
271 if n + add > T::max_value() {
272 return Err(AlphaIdError::Overflow);
273 }
274 n = n + add;
275 }
276 None => return Err(AlphaIdError::UnexpectedChar),
277 }
278 i += 1;
279 }
280
281 Ok(n)
282 }
283}