base64url_nopad

base64url without padding library.
git clone https://git.philomathiclife.com/repos/base64url_nopad
Log | Files | Refs | README

commit 5662c167bc54291e64cb32bd309ee4d3f841b9f0
parent d4d6190c1cf37ec44e0cb488326425ca1b669227
Author: Zack Newman <zack@philomathiclife.com>
Date:   Sun, 12 Jul 2026 13:45:33 -0600

bump msrv. update deps. new lints. move unit tests

Diffstat:
MCargo.toml | 8+++++---
Msrc/lib.rs | 7++-----
Dsrc/test.rs | 153-------------------------------------------------------------------------------
Asrc/tests.rs | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 256 insertions(+), 161 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "base64url_nopad" readme = "README.md" repository = "https://git.philomathiclife.com/repos/base64url_nopad/" -rust-version = "1.96.0" +rust-version = "1.97.0" version = "0.2.0" [lints.rust] @@ -28,13 +28,14 @@ unused = { level = "deny", priority = -1 } warnings = { level = "deny", priority = -1 } ambiguous-negative-literals = { level = "deny", priority = -1 } closure-returning-async-block = { level = "deny", priority = -1 } +dead-code-pub-in-binary = { level = "deny", priority = -1 } deprecated-in-future = { level = "deny", priority = -1 } +#deprecated-llvm-intrinsic = { level = "deny", priority = -1 } deref-into-dyn-supertrait = { level = "deny", priority = -1 } ffi-unwind-calls = { level = "deny", priority = -1 } #fuzzy-provenance-casts = { level = "deny", priority = -1 } impl-trait-redundant-captures = { level = "deny", priority = -1 } linker-info = { level = "deny", priority = -1 } -linker-messages = { level = "deny", priority = -1 } #lossy-provenance-casts = { level = "deny", priority = -1 } macro-use-extern-crate = { level = "deny", priority = -1 } meta-variable-misuse = { level = "deny", priority = -1 } @@ -80,6 +81,7 @@ arbitrary_source_item_ordering = "allow" blanket_clippy_restriction_lints = "allow" exhaustive_enums = "allow" implicit_return = "allow" +inline_trait_bounds = "allow" min_ident_chars = "allow" missing_trait_methods = "allow" question_mark_used = "allow" @@ -109,7 +111,7 @@ targets = [ ] [dev-dependencies] -rand = { version = "0.10.1", default-features = false, features = ["sys_rng"] } +rand = { version = "0.10.2", default-features = false, features = ["sys_rng"] } ### FEATURES ################################################################# diff --git a/src/lib.rs b/src/lib.rs @@ -59,10 +59,7 @@ target_pointer_width = "64" ) ), - allow( - linker_messages, - reason = "getrandom produces a linker message on macos" - ) + allow(linker_info, reason = "getrandom causes linker-info to fire on macos") )] #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -70,7 +67,7 @@ extern crate alloc; /// Unit tests. #[cfg(test)] -mod test; +mod tests; #[cfg(any(doc, feature = "alloc"))] use alloc::{collections::TryReserveError, string::String, vec::Vec}; use core::{ diff --git a/src/test.rs b/src/test.rs @@ -1,153 +0,0 @@ -#[cfg(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64", -))] -use super::MAX_ENCODE_INPUT_LEN; -#[cfg(feature = "alloc")] -use alloc::string::String; -#[cfg(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64", -))] -use rand::{RngExt as _, rngs::SmallRng}; -#[expect( - clippy::as_conversions, - clippy::cast_possible_truncation, - reason = "comment justifies correctness" -)] -#[cfg(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64", -))] -#[ignore = "slow"] -#[test] -fn encode_decode_len() { - assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1); - let mut rng = rand::make_rng::<SmallRng>(); - for _ in 0u32..10_000_000 { - // `uN as usize` is fine since we `cfg` by pointer width. - #[cfg(target_pointer_width = "16")] - let len = rng.random::<u16>() as usize; - #[cfg(target_pointer_width = "32")] - let len = rng.random::<u32>() as usize; - #[cfg(target_pointer_width = "64")] - let len = rng.random::<u64>() as usize; - if len <= MAX_ENCODE_INPUT_LEN { - assert_eq!( - super::encode_len_checked(len).map(super::decode_len), - Some(Some(len)) - ); - } else { - assert!(super::encode_len_checked(len).is_none()); - } - } - for i in 0..1025 { - assert_eq!( - super::encode_len_checked(i).map(super::decode_len), - Some(Some(i)) - ); - } - #[cfg(target_pointer_width = "16")] - for i in MAX_ENCODE_INPUT_LEN + 1.. { - assert!(super::encode_len_checked(i).is_none()); - } - #[cfg(not(target_pointer_width = "16"))] - for i in MAX_ENCODE_INPUT_LEN + 1..MAX_ENCODE_INPUT_LEN + 1_000_000 { - assert!(super::encode_len_checked(i).is_none()); - } - assert!(super::encode_len_checked(usize::MAX).is_none()); - assert_eq!( - super::encode_len_checked(MAX_ENCODE_INPUT_LEN), - Some(usize::MAX) - ); - for _ in 0u32..10_000_000 { - #[cfg(target_pointer_width = "16")] - let len = rng.random::<u16>() as usize; - #[cfg(target_pointer_width = "32")] - let len = rng.random::<u32>() as usize; - #[cfg(target_pointer_width = "64")] - let len = rng.random::<u64>() as usize; - if len & 3 == 1 { - assert!(super::decode_len(len).is_none()); - } else { - assert_eq!( - super::decode_len(len).map(super::encode_len_checked), - Some(Some(len)) - ); - } - } - for i in 0..1025 { - if i & 3 == 1 { - assert!(super::decode_len(i).is_none()); - } else { - assert_eq!( - super::decode_len(i).map(super::encode_len_checked), - Some(Some(i)) - ); - } - } - #[cfg(target_pointer_width = "16")] - for i in 0..=usize::MAX { - if i & 3 == 1 { - assert!(super::decode_len(i).is_none()); - } else { - assert_eq!( - super::decode_len(i).map(super::encode_len_checked), - Some(Some(i)) - ); - } - } - #[cfg(not(target_pointer_width = "16"))] - for i in usize::MAX - 1_000_000..=usize::MAX { - if i & 3 == 1 { - assert!(super::decode_len(i).is_none()); - } else { - assert_eq!( - super::decode_len(i).map(super::encode_len_checked), - Some(Some(i)) - ); - } - } - assert_eq!(super::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN)); -} -#[expect(clippy::indexing_slicing, reason = "comments justify correctness")] -#[cfg(feature = "alloc")] -#[test] -fn encode_write() { - let input = [9; 8192]; - let mut buffer = String::with_capacity(super::encode_len(input.len())); - let cap = buffer.capacity(); - let mut write_len; - for len in 0..input.len() { - write_len = super::encode_len(len); - match write_len.checked_add(buffer.len()) { - None => { - buffer.clear(); - // Indexing is fine since `len <= input.len()`. - assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); - assert_eq!(buffer.len(), write_len); - } - Some(l) => { - if l > cap { - buffer.clear(); - // Indexing is fine since `len <= input.len()`. - assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); - assert_eq!(buffer.len(), write_len); - } else { - // Indexing is fine since `len <= input.len()`. - assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); - assert_eq!(buffer.len(), l); - } - } - } - assert!( - buffer - .as_bytes() - .iter() - .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') }) - ); - } -} diff --git a/src/tests.rs b/src/tests.rs @@ -0,0 +1,249 @@ +use super::DecodeErr; +#[cfg(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64", +))] +use super::MAX_ENCODE_INPUT_LEN; +#[cfg(feature = "alloc")] +use alloc::{borrow::ToOwned as _, string::String, vec}; +use core::fmt::{self, Write}; +#[cfg(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64", +))] +use rand::{RngExt as _, rngs::SmallRng}; +#[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "comment justifies correctness" +)] +#[cfg(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64", +))] +#[ignore = "slow"] +#[test] +fn encode_decode_len() { + assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1); + let mut rng = rand::make_rng::<SmallRng>(); + for _ in 0u32..10_000_000 { + // `uN as usize` is fine since we `cfg` by pointer width. + #[cfg(target_pointer_width = "16")] + let len = rng.random::<u16>() as usize; + #[cfg(target_pointer_width = "32")] + let len = rng.random::<u32>() as usize; + #[cfg(target_pointer_width = "64")] + let len = rng.random::<u64>() as usize; + if len <= MAX_ENCODE_INPUT_LEN { + assert_eq!( + super::encode_len_checked(len).map(super::decode_len), + Some(Some(len)) + ); + } else { + assert!(super::encode_len_checked(len).is_none()); + } + } + for i in 0..1025 { + assert_eq!( + super::encode_len_checked(i).map(super::decode_len), + Some(Some(i)) + ); + } + #[cfg(target_pointer_width = "16")] + for i in MAX_ENCODE_INPUT_LEN + 1.. { + assert!(super::encode_len_checked(i).is_none()); + } + #[cfg(not(target_pointer_width = "16"))] + for i in MAX_ENCODE_INPUT_LEN + 1..MAX_ENCODE_INPUT_LEN + 1_000_000 { + assert!(super::encode_len_checked(i).is_none()); + } + assert!(super::encode_len_checked(usize::MAX).is_none()); + assert_eq!( + super::encode_len_checked(MAX_ENCODE_INPUT_LEN), + Some(usize::MAX) + ); + for _ in 0u32..10_000_000 { + #[cfg(target_pointer_width = "16")] + let len = rng.random::<u16>() as usize; + #[cfg(target_pointer_width = "32")] + let len = rng.random::<u32>() as usize; + #[cfg(target_pointer_width = "64")] + let len = rng.random::<u64>() as usize; + if len & 3 == 1 { + assert!(super::decode_len(len).is_none()); + } else { + assert_eq!( + super::decode_len(len).map(super::encode_len_checked), + Some(Some(len)) + ); + } + } + for i in 0..1025 { + if i & 3 == 1 { + assert!(super::decode_len(i).is_none()); + } else { + assert_eq!( + super::decode_len(i).map(super::encode_len_checked), + Some(Some(i)) + ); + } + } + #[cfg(target_pointer_width = "16")] + for i in 0..=usize::MAX { + if i & 3 == 1 { + assert!(super::decode_len(i).is_none()); + } else { + assert_eq!( + super::decode_len(i).map(super::encode_len_checked), + Some(Some(i)) + ); + } + } + #[cfg(not(target_pointer_width = "16"))] + for i in usize::MAX - 1_000_000..=usize::MAX { + if i & 3 == 1 { + assert!(super::decode_len(i).is_none()); + } else { + assert_eq!( + super::decode_len(i).map(super::encode_len_checked), + Some(Some(i)) + ); + } + } + assert_eq!(super::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN)); +} +#[expect(clippy::indexing_slicing, reason = "comments justify correctness")] +#[cfg(feature = "alloc")] +#[test] +fn encode_write() { + let input = [9; 8192]; + let mut buffer = String::with_capacity(super::encode_len(input.len())); + let cap = buffer.capacity(); + let mut write_len; + for len in 0..input.len() { + write_len = super::encode_len(len); + match write_len.checked_add(buffer.len()) { + None => { + buffer.clear(); + // Indexing is fine since `len <= input.len()`. + assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); + assert_eq!(buffer.len(), write_len); + } + Some(l) => { + if l > cap { + buffer.clear(); + // Indexing is fine since `len <= input.len()`. + assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); + assert_eq!(buffer.len(), write_len); + } else { + // Indexing is fine since `len <= input.len()`. + assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); + assert_eq!(buffer.len(), l); + } + } + } + assert!( + buffer + .as_bytes() + .iter() + .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') }) + ); + } +} +#[cfg(feature = "alloc")] +#[test] +fn try_encode() { + assert_eq!(super::try_encode(&[0]), Ok("AA".to_owned())); +} +#[cfg(feature = "alloc")] +#[test] +fn encode() { + assert_eq!(super::encode(&[0]), "AA".to_owned()); +} +#[cfg(feature = "alloc")] +#[test] +fn try_encode_append() { + let mut val = "Hi".to_owned(); + assert_eq!( + super::try_encode_append(&[0], &mut val), + Ok("AA".to_owned().as_mut_str()) + ); + assert_eq!(val, "HiAA"); +} +#[cfg(feature = "alloc")] +#[test] +fn encode_append() { + let mut val = "Hi".to_owned(); + assert_eq!( + super::encode_append(&[0], &mut val), + "AA".to_owned().as_mut_str() + ); + assert_eq!(val, "HiAA"); +} +#[cfg(feature = "alloc")] +#[test] +fn try_encode_append_only() { + let mut val = "Hi".to_owned(); + assert_eq!(super::try_encode_append_only(&[0], &mut val), Ok(())); + assert_eq!(val, "HiAA"); +} +#[cfg(feature = "alloc")] +#[test] +fn encode_append_only() { + let mut val = "Hi".to_owned(); + super::encode_append_only(&[0], &mut val); + assert_eq!(val, "HiAA"); +} +#[test] +fn decode_buffer() { + assert_eq!( + super::decode_buffer(b"AA", &mut [0]), + Ok([0].as_mut_slice()) + ); + assert_eq!( + super::decode_buffer(b"AA", &mut []), + Err(DecodeErr::BufferLen) + ); +} +#[cfg(feature = "alloc")] +#[test] +fn decode() { + assert_eq!(super::decode(b"AA"), Ok(vec![0])); +} +#[test] +fn validate_encoded_data() { + assert_eq!(super::validate_encoded_data(b"AA"), Ok(())); +} +#[test] +fn encode_buffer_exact() { + let mut output = [0; 2]; + super::encode_buffer_exact(&[0], &mut output); + assert_eq!(output, *b"AA"); +} +#[test] +fn decode_buffer_exact() { + let mut output = [1]; + assert_eq!(super::decode_buffer_exact(b"AA", &mut output), Ok(())); + assert_eq!(output, [0]); +} +#[test] +fn encode_write_core() { + struct Writer<'a>(&'a mut [u8]); + impl Write for Writer<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.get_mut(..s.len()).ok_or(fmt::Error).map(|bytes| { + bytes.copy_from_slice(s.as_bytes()); + }) + } + } + let mut writer = Writer(&mut [0, 0, 1]); + assert_eq!(super::encode_write(&[0], &mut writer), Ok(())); + assert_eq!(writer.0, b"AA\x01"); + assert_eq!( + super::encode_write(&[0, 0, 0], &mut writer), + Err(fmt::Error) + ); +}