This commit is contained in:
Ivan Akimov 2023-07-18 14:32:39 -05:00
parent f2d11a023e
commit cf377536d0
7 changed files with 389 additions and 271 deletions

1
.clippy.toml Normal file
View File

@ -0,0 +1 @@
too-many-arguments-threshold = 10

1
.gitattributes vendored
View File

@ -1 +1,2 @@
**/blocklist.json binary
Cargo.lock binary

View File

@ -1,5 +1,7 @@
# [Sqids Rust](https://sqids.org/rust)
[![Github Actions](https://img.shields.io/github/actions/workflow/status/sqids/sqids-rust/tests.yml)](https://github.com/sqids/sqids-rust/actions)
Sqids (pronounced "squids") is a small library that lets you generate YouTube-looking IDs from numbers. It's good for link shortening, fast & URL-safe ID generation and decoding back into numbers for quicker database lookups.
## Getting started

2
release.toml Normal file
View File

@ -0,0 +1,2 @@
consolidate-commits = false
consolidate-pushes = true

13
rustfmt.toml Normal file
View File

@ -0,0 +1,13 @@
max_width = 100
comment_width = 100
hard_tabs = true
edition = "2021"
reorder_imports = true
imports_granularity = "Crate"
use_small_heuristics = "Max"
wrap_comments = true
binop_separator = "Back"
trailing_comma = "Vertical"
trailing_semicolon = true
use_field_init_shorthand = true
format_macro_bodies = true

View File

@ -1,6 +1,5 @@
use derive_more::Display;
use std::collections::HashSet;
use std::result;
use std::{collections::HashSet, result};
#[derive(Display, Debug)]
pub enum Error {
@ -18,6 +17,10 @@ pub enum Error {
pub type Result<T> = result::Result<T, Error>;
pub fn default_blocklist() -> HashSet<String> {
serde_json::from_str(include_str!("blocklist.json")).unwrap()
}
#[derive(Debug)]
pub struct Options {
alphabet: String,
@ -52,7 +55,7 @@ impl Default for Options {
Options {
alphabet: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".to_string(),
min_length: 0,
blocklist: serde_json::from_str(include_str!("blocklist.json")).unwrap(),
blocklist: default_blocklist(),
}
}
}
@ -91,14 +94,11 @@ impl Sqids {
})
.collect();
let mut sqids = Sqids {
alphabet,
min_length: options.min_length,
blocklist: filtered_blocklist,
};
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()
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,
@ -122,10 +122,7 @@ impl Sqids {
.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)
@ -145,14 +142,8 @@ impl Sqids {
let prefix = id.chars().next().unwrap();
let offset = self.alphabet.iter().position(|&c| c == prefix).unwrap();
let mut alphabet: Vec<char> = self
.alphabet
.iter()
.cycle()
.skip(offset)
.take(self.alphabet.len())
.copied()
.collect();
let mut alphabet: Vec<char> =
self.alphabet.iter().cycle().skip(offset).take(self.alphabet.len()).copied().collect();
let partition = alphabet[1];
@ -199,22 +190,12 @@ impl Sqids {
}
fn encode_numbers(&self, numbers: &[u64], partitioned: bool) -> Result<String> {
let offset = numbers
.iter()
.enumerate()
.fold(numbers.len(), |a, (i, &v)| {
let offset = numbers.iter().enumerate().fold(numbers.len(), |a, (i, &v)| {
self.alphabet[v as usize % self.alphabet.len()] as usize + i + a
})
% self.alphabet.len();
}) % self.alphabet.len();
let mut alphabet: Vec<char> = self
.alphabet
.iter()
.cycle()
.skip(offset)
.take(self.alphabet.len())
.copied()
.collect();
let mut alphabet: Vec<char> =
self.alphabet.iter().cycle().skip(offset).take(self.alphabet.len()).copied().collect();
let prefix = alphabet[0];
let partition = alphabet[1];

View File

@ -2,10 +2,128 @@ use sqids::*;
#[test]
fn simple() {
let id = "8QRLaD".to_string();
let numbers = vec![1, 2, 3];
let sqids = Sqids::new(None).unwrap();
let numbers = vec![1, 2, 3];
let id = "8QRLaD";
assert_eq!(sqids.encode(&numbers).unwrap(), id);
assert_eq!(sqids.decode(&id), numbers);
assert_eq!(sqids.decode(id), numbers);
}
#[test]
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()];
assert_eq!(sqids.decode(&sqids.encode(&numbers).unwrap()), numbers);
}
#[test]
fn incremental_numbers() {
let sqids = Sqids::new(None).unwrap();
let ids = vec![
("bV", vec![0]),
("U9", vec![1]),
("g8", vec![2]),
("Ez", vec![3]),
("V8", vec![4]),
("ul", vec![5]),
("O3", vec![6]),
("AF", vec![7]),
("ph", vec![8]),
("n8", vec![9]),
];
for (id, numbers) in ids {
assert_eq!(sqids.encode(&numbers).unwrap(), id);
assert_eq!(sqids.decode(id), numbers);
}
}
#[test]
fn incremental_numbers_same_index_0() {
let sqids = Sqids::new(None).unwrap();
let ids = vec![
("SrIu", vec![0, 0]),
("nZqE", vec![0, 1]),
("tJyf", vec![0, 2]),
("e86S", vec![0, 3]),
("rtC7", vec![0, 4]),
("sQ8R", vec![0, 5]),
("uz2n", vec![0, 6]),
("7Td9", vec![0, 7]),
("3nWE", vec![0, 8]),
("mIxM", vec![0, 9]),
];
for (id, numbers) in ids {
assert_eq!(sqids.encode(&numbers).unwrap(), id);
assert_eq!(sqids.decode(id), numbers);
}
}
#[test]
fn incremental_numbers_same_index_1() {
let sqids = Sqids::new(None).unwrap();
let ids = vec![
("SrIu", vec![0, 0]),
("nbqh", vec![1, 0]),
("t4yj", vec![2, 0]),
("eQ6L", vec![3, 0]),
("r4Cc", vec![4, 0]),
("sL82", vec![5, 0]),
("uo2f", vec![6, 0]),
("7Zdq", vec![7, 0]),
("36Wf", vec![8, 0]),
("m4xT", vec![9, 0]),
];
for (id, numbers) in ids {
assert_eq!(sqids.encode(&numbers).unwrap(), id);
assert_eq!(sqids.decode(id), numbers);
}
}
#[test]
fn multi_input() {
let sqids = Sqids::new(None).unwrap();
let numbers: Vec<u64> = (0..100).collect();
let output = sqids.decode(&sqids.encode(&numbers).unwrap());
assert_eq!(numbers, output);
}
#[test]
fn encoding_no_numbers() {
let sqids = Sqids::new(None).unwrap();
assert_eq!(sqids.encode(&[]).unwrap(), "");
}
#[test]
fn decoding_empty_string() {
let sqids = Sqids::new(None).unwrap();
let numbers: Vec<u64> = vec![];
assert_eq!(sqids.decode(""), numbers);
}
#[test]
fn decoding_invalid_character() {
let sqids = Sqids::new(None).unwrap();
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());
}