diff --git a/rustfmt.toml b/rustfmt.toml index b4e9813..d79c9cf 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -10,4 +10,5 @@ binop_separator = "Back" trailing_comma = "Vertical" trailing_semicolon = true use_field_init_shorthand = true -format_macro_bodies = true \ No newline at end of file +format_macro_bodies = true +format_code_in_doc_comments = true diff --git a/src/lib.rs b/src/lib.rs index be9054f..8100347 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#![warn(missing_docs)] +#![allow(clippy::tabs_in_doc_comments)] + #![doc = include_str!("../README.md")] // Make the link to the LICENSE in README.md work. @@ -13,34 +16,78 @@ use std::{cmp::min, collections::HashSet, result}; use derive_builder::Builder; use thiserror::Error; +/// sqids Error type. #[derive(Error, Debug, Eq, PartialEq)] pub enum Error { + /// Alphabet cannot contain multibyte characters + /// + /// ``` + /// # use sqids::{Sqids, Error}; + /// let error = Sqids::builder().alphabet("☃️🦀🔥".chars().collect()).build().unwrap_err(); + /// assert_eq!(error, Error::AlphabetMultibyteCharacters); + /// ``` #[error("Alphabet cannot contain multibyte characters")] AlphabetMultibyteCharacters, + /// Alphabet length must be at least 3 + /// + /// ``` + /// # use sqids::{Sqids, Error}; + /// let error = Sqids::builder().alphabet("ab".chars().collect()).build().unwrap_err(); + /// assert_eq!(error, Error::AlphabetLength); + /// ``` #[error("Alphabet length must be at least 3")] AlphabetLength, + /// Alphabet must contain unique characters + /// + /// ``` + /// # use sqids::{Sqids, Error}; + /// let error = Sqids::builder().alphabet("aba".chars().collect()).build().unwrap_err(); + /// assert_eq!(error, Error::AlphabetUniqueCharacters); + /// ``` #[error("Alphabet must contain unique characters")] AlphabetUniqueCharacters, + /// Reached max attempts to re-generate the ID + /// + /// ``` + /// # use sqids::{Sqids, Error}; + /// let sqids = Sqids::builder() + /// .alphabet("abc".chars().collect()) + /// .min_length(3) + /// .blocklist(["aac".to_string(), "bba".to_string(), "ccb".to_string()].into()) + /// .build() + /// .unwrap(); + /// let error = sqids.encode(&[1]).unwrap_err(); + /// assert_eq!(error, Error::BlocklistMaxAttempts); + /// ``` #[error("Reached max attempts to re-generate the ID")] BlocklistMaxAttempts, } +/// type alias for Result pub type Result = result::Result; +/// The default alphabet used when none is given when creating a [Sqids]. pub const DEFAULT_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +/// Returns the default blocklist when none is given when creating a [Sqids]. pub fn default_blocklist() -> HashSet { serde_json::from_str(include_str!("blocklist.json")).unwrap() } +/// Options for creating a [Sqids]. #[derive(Debug)] pub struct Options { + /// The [Sqids] alphabet. pub alphabet: String, + /// The minimum length of a sqid. pub min_length: u8, + /// Blocklist. When creating a sqid [Sqids] will try to avoid generating a string that begins + /// with one of these. pub blocklist: HashSet, } impl Options { + /// Create an [Options] object. pub fn new( alphabet: Option, min_length: Option, @@ -72,11 +119,16 @@ impl Default for Options { } } +/// A generator for sqids. #[derive(Debug, Builder)] #[builder(build_fn(skip, error = "Error"), pattern = "owned")] pub struct Sqids { + /// The alphabet that is being used when generating sqids. alphabet: Vec, + /// The minimum length of a sqid. min_length: u8, + /// Blocklist. When creating a sqid strings that begins + /// with one of these will be avoided. blocklist: HashSet, } @@ -87,10 +139,12 @@ impl Default for Sqids { } impl SqidsBuilder { + /// Create a [SqidsBuilder]. pub fn new() -> Self { Self::default() } + /// Build a [Sqids] object. pub fn build(self) -> Result { let alphabet: Vec = self.alphabet.unwrap_or_else(|| DEFAULT_ALPHABET.chars().collect()); @@ -135,6 +189,7 @@ impl SqidsBuilder { } impl Sqids { + /// Create a [Sqids] from [Options]. pub fn new(options: Option) -> Result { let options = options.unwrap_or_default(); Self::builder() @@ -144,10 +199,17 @@ impl Sqids { .build() } + /// Create a [SqidsBuilder]. pub fn builder() -> SqidsBuilder { SqidsBuilder::default() } + /// Generate a sqid from a slice of numbers. + /// + /// When an sqid is generated it is checked against the [SqidsBuilder::blocklist]. When a + /// blocked word is encountered another attempt is made by shifting the alphabet. + /// When the alphabet is exhausted and all possible sqids for this input are blocked + /// [Error::BlocklistMaxAttempts] is returned. pub fn encode(&self, numbers: &[u64]) -> Result { if numbers.is_empty() { return Ok(String::new()); @@ -156,6 +218,8 @@ impl Sqids { self.encode_numbers(numbers, 0) } + /// Decode a sqid into a vector of numbers. When an invalid sqid is encountered an empty vector + /// is returned. pub fn decode(&self, id: &str) -> Vec { let mut ret = Vec::new();