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}