mirror of
https://github.com/sqids/sqids-rust
synced 2024-11-21 17:20:05 +00:00
wip
This commit is contained in:
parent
cf377536d0
commit
6e189eb22f
44
src/lib.rs
44
src/lib.rs
@ -1,7 +1,7 @@
|
||||
use derive_more::Display;
|
||||
use std::{collections::HashSet, result};
|
||||
|
||||
#[derive(Display, Debug)]
|
||||
#[derive(Display, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
#[display(fmt = "Alphabet length must be at least 5")]
|
||||
AlphabetLength,
|
||||
@ -23,9 +23,9 @@ pub fn default_blocklist() -> HashSet<String> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
alphabet: String,
|
||||
min_length: usize,
|
||||
blocklist: HashSet<String>,
|
||||
pub alphabet: String,
|
||||
pub min_length: usize,
|
||||
pub blocklist: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
@ -81,6 +81,15 @@ impl Sqids {
|
||||
return Err(Error::AlphabetUniqueCharacters);
|
||||
}
|
||||
|
||||
if options.min_length < Self::min_value() as usize ||
|
||||
options.min_length > options.alphabet.len()
|
||||
{
|
||||
return Err(Error::MinLength {
|
||||
min: Self::min_value() as usize,
|
||||
max: options.alphabet.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let filtered_blocklist: HashSet<String> = options
|
||||
.blocklist
|
||||
.iter()
|
||||
@ -97,15 +106,6 @@ impl Sqids {
|
||||
let mut sqids =
|
||||
Sqids { alphabet, min_length: options.min_length, blocklist: filtered_blocklist };
|
||||
|
||||
if options.min_length < sqids.min_value() as usize ||
|
||||
options.min_length > options.alphabet.len()
|
||||
{
|
||||
return Err(Error::MinLength {
|
||||
min: sqids.min_value() as usize,
|
||||
max: options.alphabet.len(),
|
||||
});
|
||||
}
|
||||
|
||||
sqids.alphabet = sqids.shuffle(&sqids.alphabet);
|
||||
Ok(sqids)
|
||||
}
|
||||
@ -118,11 +118,11 @@ impl Sqids {
|
||||
let in_range_numbers: Vec<u64> = numbers
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&n| n >= self.min_value() && n <= self.max_value())
|
||||
.filter(|&n| n >= Self::min_value() && n <= Self::max_value())
|
||||
.collect();
|
||||
|
||||
if in_range_numbers.len() != numbers.len() {
|
||||
return Err(Error::EncodingRange { min: self.min_value(), max: self.max_value() });
|
||||
return Err(Error::EncodingRange { min: Self::min_value(), max: Self::max_value() });
|
||||
}
|
||||
|
||||
self.encode_numbers(&in_range_numbers, false)
|
||||
@ -181,11 +181,11 @@ impl Sqids {
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn min_value(&self) -> u64 {
|
||||
pub fn min_value() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn max_value(&self) -> u64 {
|
||||
pub fn max_value() -> u64 {
|
||||
u64::MAX
|
||||
}
|
||||
|
||||
@ -233,11 +233,9 @@ impl Sqids {
|
||||
}
|
||||
|
||||
if self.min_length > id.len() {
|
||||
let mut new_id = id.clone();
|
||||
let alphabet_slice = &alphabet[..(self.min_length - id.len())];
|
||||
new_id.push_str(&alphabet_slice.iter().collect::<String>());
|
||||
new_id.push_str(&id[1..]);
|
||||
id = new_id;
|
||||
id = id[..1].to_string() +
|
||||
&alphabet[..(self.min_length - id.len())].iter().collect::<String>() +
|
||||
&id[1..]
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +243,7 @@ impl Sqids {
|
||||
let mut new_numbers;
|
||||
|
||||
if partitioned {
|
||||
if numbers[0] + 1 > self.max_value() {
|
||||
if numbers[0] + 1 > Self::max_value() {
|
||||
return Err(Error::BlocklistOutOfRange);
|
||||
} else {
|
||||
new_numbers = numbers.to_vec();
|
||||
|
49
tests/alphabet.rs
Normal file
49
tests/alphabet.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use sqids::*;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let sqids =
|
||||
Sqids::new(Some(Options::new(Some("0123456789abcdef".to_string()), None, None))).unwrap();
|
||||
|
||||
let numbers = vec![1, 2, 3];
|
||||
let id = "4d9fd2";
|
||||
|
||||
assert_eq!(sqids.encode(&numbers).unwrap(), id);
|
||||
assert_eq!(sqids.decode(id), numbers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_alphabet() {
|
||||
let sqids = Sqids::new(Some(Options::new(Some("abcde".to_string()), None, None))).unwrap();
|
||||
|
||||
let numbers = vec![1, 2, 3];
|
||||
assert_eq!(sqids.decode(&sqids.encode(&numbers).unwrap()), numbers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_alphabet() {
|
||||
let sqids = Sqids::new(Some(Options::new(
|
||||
Some("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_+|{}[];:'\"/?.>,<`~".to_string()),
|
||||
None,
|
||||
None,
|
||||
))).unwrap();
|
||||
|
||||
let numbers = vec![1, 2, 3];
|
||||
assert_eq!(sqids.decode(&sqids.encode(&numbers).unwrap()), numbers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeating_alphabet_characters() {
|
||||
assert_eq!(
|
||||
Sqids::new(Some(Options::new(Some("aabcdefg".to_string()), None, None,))).err().unwrap(),
|
||||
Error::AlphabetUniqueCharacters
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_short_alphabet() {
|
||||
assert_eq!(
|
||||
Sqids::new(Some(Options::new(Some("abcd".to_string()), None, None,))).err().unwrap(),
|
||||
Error::AlphabetLength
|
||||
)
|
||||
}
|
83
tests/blocklist.rs
Normal file
83
tests/blocklist.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use sqids::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn if_no_custom_blocklist_param_use_default_blocklist() {
|
||||
let sqids = Sqids::new(None).unwrap();
|
||||
|
||||
assert_eq!(sqids.decode("sexy"), vec![200044]);
|
||||
assert_eq!(sqids.encode(&[200044]).unwrap(), "d171vI");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_empty_blocklist_param_passed_dont_use_any_blocklist() {
|
||||
let sqids = Sqids::new(Some(Options::new(None, None, Some(HashSet::new())))).unwrap();
|
||||
|
||||
assert_eq!(sqids.decode("sexy"), vec![200044]);
|
||||
assert_eq!(sqids.encode(&[200044]).unwrap(), "sexy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_non_empty_blocklist_param_passed_use_only_that() {
|
||||
let sqids =
|
||||
Sqids::new(Some(Options::new(None, None, Some(HashSet::from(["AvTg".to_string()])))))
|
||||
.unwrap();
|
||||
|
||||
// make sure we don't use the default blocklist
|
||||
assert_eq!(sqids.decode("sexy"), vec![200044]);
|
||||
assert_eq!(sqids.encode(&[200044]).unwrap(), "sexy");
|
||||
|
||||
// make sure we are using the passed blocklist
|
||||
assert_eq!(sqids.decode("AvTg"), vec![100000]);
|
||||
assert_eq!(sqids.encode(&[100000]).unwrap(), "7T1X8k");
|
||||
assert_eq!(sqids.decode("7T1X8k"), vec![100000]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocklist() {
|
||||
let sqids = Sqids::new(Some(Options::new(
|
||||
None,
|
||||
None,
|
||||
Some(HashSet::from([
|
||||
"8QRLaD".to_owned(), // normal result of 1st encoding, let's block that word on purpose
|
||||
"7T1cd0dL".to_owned(), // result of 2nd encoding
|
||||
"UeIe".to_owned(), // result of 3rd encoding is `RA8UeIe7`, let's block a substring
|
||||
"imhw".to_owned(), // result of 4th encoding is `WM3Limhw`, let's block the postfix
|
||||
"LfUQ".to_owned(), // result of 4th encoding is `LfUQh4HN`, let's block the prefix
|
||||
])),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(sqids.encode(&[1, 2, 3]).unwrap(), "TM0x1Mxz");
|
||||
assert_eq!(sqids.decode("TM0x1Mxz"), vec![1, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoding_blocklist_words_should_still_work() {
|
||||
let sqids = Sqids::new(Some(Options::new(
|
||||
None,
|
||||
None,
|
||||
Some(HashSet::from([
|
||||
"8QRLaD".to_owned(),
|
||||
"7T1cd0dL".to_owned(),
|
||||
"RA8UeIe7".to_owned(),
|
||||
"WM3Limhw".to_owned(),
|
||||
"LfUQh4HN".to_owned(),
|
||||
])),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(sqids.decode("8QRLaD"), vec![1, 2, 3]);
|
||||
assert_eq!(sqids.decode("7T1cd0dL"), vec![1, 2, 3]);
|
||||
assert_eq!(sqids.decode("RA8UeIe7"), vec![1, 2, 3]);
|
||||
assert_eq!(sqids.decode("WM3Limhw"), vec![1, 2, 3]);
|
||||
assert_eq!(sqids.decode("LfUQh4HN"), vec![1, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_against_short_blocklist_word() {
|
||||
let sqids = Sqids::new(Some(Options::new(None, None, Some(HashSet::from(["pPQ".to_owned()])))))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(sqids.decode(&sqids.encode(&[1000]).unwrap()), vec![1000]);
|
||||
}
|
@ -15,7 +15,7 @@ fn simple() {
|
||||
fn different_inputs() {
|
||||
let sqids = Sqids::new(None).unwrap();
|
||||
|
||||
let numbers = vec![0, 0, 0, 1, 2, 3, 100, 1_000, 100_000, 1_000_000, sqids.max_value()];
|
||||
let numbers = vec![0, 0, 0, 1, 2, 3, 100, 1_000, 100_000, 1_000_000, Sqids::max_value()];
|
||||
|
||||
assert_eq!(sqids.decode(&sqids.encode(&numbers).unwrap()), numbers);
|
||||
}
|
||||
@ -118,12 +118,3 @@ fn decoding_invalid_character() {
|
||||
let numbers: Vec<u64> = vec![];
|
||||
assert_eq!(sqids.decode("*"), numbers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn encode_out_of_range_numbers() {
|
||||
let sqids = Sqids::new(None).unwrap();
|
||||
|
||||
assert!(sqids.encode(&[sqids.min_value() - 1]).is_err());
|
||||
assert!(sqids.encode(&[sqids.max_value() + 1]).is_err());
|
||||
}
|
||||
|
68
tests/minlength.rs
Normal file
68
tests/minlength.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use sqids::*;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let sqids = Sqids::new(Some(Options::new(None, Some(Options::default().alphabet.len()), None)))
|
||||
.unwrap();
|
||||
|
||||
let numbers = vec![1, 2, 3];
|
||||
let id = "75JILToVsGerOADWmHlY38xvbaNZKQ9wdFS0B6kcMEtnRpgizhjU42qT1cd0dL".to_owned();
|
||||
|
||||
assert_eq!(sqids.encode(&numbers).unwrap(), id);
|
||||
assert_eq!(sqids.decode(&id), numbers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incremental_numbers() {
|
||||
let sqids = Sqids::new(Some(Options::new(None, Some(Options::default().alphabet.len()), None)))
|
||||
.unwrap();
|
||||
|
||||
let ids = vec![
|
||||
("jf26PLNeO5WbJDUV7FmMtlGXps3CoqkHnZ8cYd19yIiTAQuvKSExzhrRghBlwf".to_owned(), vec![0, 0]),
|
||||
("vQLUq7zWXC6k9cNOtgJ2ZK8rbxuipBFAS10yTdYeRa3ojHwGnmMV4PDhESI2jL".to_owned(), vec![0, 1]),
|
||||
("YhcpVK3COXbifmnZoLuxWgBQwtjsSaDGAdr0ReTHM16yI9vU8JNzlFq5Eu2oPp".to_owned(), vec![0, 2]),
|
||||
("OTkn9daFgDZX6LbmfxI83RSKetJu0APihlsrYoz5pvQw7GyWHEUcN2jBqd4kJ9".to_owned(), vec![0, 3]),
|
||||
("h2cV5eLNYj1x4ToZpfM90UlgHBOKikQFvnW36AC8zrmuJ7XdRytIGPawqYEbBe".to_owned(), vec![0, 4]),
|
||||
("7Mf0HeUNkpsZOTvmcj836P9EWKaACBubInFJtwXR2DSzgYGhQV5i4lLxoT1qdU".to_owned(), vec![0, 5]),
|
||||
("APVSD1ZIY4WGBK75xktMfTev8qsCJw6oyH2j3OnLcXRlhziUmpbuNEar05QCsI".to_owned(), vec![0, 6]),
|
||||
("P0LUhnlT76rsWSofOeyRGQZv1cC5qu3dtaJYNEXwk8Vpx92bKiHIz4MgmiDOF7".to_owned(), vec![0, 7]),
|
||||
("xAhypZMXYIGCL4uW0te6lsFHaPc3SiD1TBgw5O7bvodzjqUn89JQRfk2Nvm4JI".to_owned(), vec![0, 8]),
|
||||
("94dRPIZ6irlXWvTbKywFuAhBoECQOVMjDJp53s2xeqaSzHY8nc17tmkLGwfGNl".to_owned(), vec![0, 9]),
|
||||
];
|
||||
|
||||
for (id, numbers) in ids {
|
||||
assert_eq!(sqids.encode(&numbers).unwrap(), id);
|
||||
assert_eq!(sqids.decode(&id), numbers);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_lengths() {
|
||||
for &min_length in &[0, 1, 5, 10, Options::default().alphabet.len()] {
|
||||
for numbers in &[
|
||||
vec![Sqids::min_value()],
|
||||
vec![0, 0, 0, 0, 0],
|
||||
vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
vec![100, 200, 300],
|
||||
vec![1_000, 2_000, 3_000],
|
||||
vec![1_000_000],
|
||||
vec![Sqids::max_value()],
|
||||
] {
|
||||
let sqids = Sqids::new(Some(Options::new(None, Some(min_length), None))).unwrap();
|
||||
|
||||
let id = sqids.encode(&numbers).unwrap();
|
||||
assert!(id.len() >= min_length);
|
||||
assert_eq!(sqids.decode(&id), *numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_range_invalid_min_length() {
|
||||
assert_eq!(
|
||||
Sqids::new(Some(Options::new(None, Some(Options::default().alphabet.len() + 1), None)))
|
||||
.err()
|
||||
.unwrap(),
|
||||
Error::MinLength { min: 0, max: Options::default().alphabet.len() }
|
||||
);
|
||||
}
|
65
tests/uniques.rs
Normal file
65
tests/uniques.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use sqids::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
const UPPER: u64 = 1_000_000;
|
||||
|
||||
#[test]
|
||||
fn uniques_with_padding() {
|
||||
let sqids = Sqids::new(Some(Options::new(None, Some(Options::default().alphabet.len()), None)))
|
||||
.unwrap();
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for i in 0..UPPER {
|
||||
let numbers = vec![i];
|
||||
let id = sqids.encode(&numbers).unwrap();
|
||||
set.insert(id.clone());
|
||||
assert_eq!(sqids.decode(&id), numbers);
|
||||
}
|
||||
|
||||
assert_eq!(set.len(), UPPER as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniques_low_ranges() {
|
||||
let sqids = Sqids::new(None).unwrap();
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for i in 0..UPPER {
|
||||
let numbers = vec![i];
|
||||
let id = sqids.encode(&numbers).unwrap();
|
||||
set.insert(id.clone());
|
||||
assert_eq!(sqids.decode(&id), numbers);
|
||||
}
|
||||
|
||||
assert_eq!(set.len(), UPPER as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniques_high_ranges() {
|
||||
let sqids = Sqids::new(None).unwrap();
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for i in 100_000_000..100_000_000 + UPPER {
|
||||
let numbers = vec![i];
|
||||
let id = sqids.encode(&numbers).unwrap();
|
||||
set.insert(id.clone());
|
||||
assert_eq!(sqids.decode(&id), numbers);
|
||||
}
|
||||
|
||||
assert_eq!(set.len(), UPPER as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniques_multi() {
|
||||
let sqids = Sqids::new(None).unwrap();
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for i in 0..UPPER {
|
||||
let numbers = vec![i, i, i, i, i];
|
||||
let id = sqids.encode(&numbers).unwrap();
|
||||
set.insert(id.clone());
|
||||
assert_eq!(sqids.decode(&id), numbers);
|
||||
}
|
||||
|
||||
assert_eq!(set.len(), UPPER as usize);
|
||||
}
|
Loading…
Reference in New Issue
Block a user