use rand::RngCore;
use rusqlite::ToSql;
use rusqlite::types::{ToSqlOutput, ValueRef};
use serde::{Deserialize, Serialize};
use std::fmt;
const BASE62_ALPHABET: &[u8; 62] =
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
fn mint_base62<const N: usize>() -> String {
let mut rng = rand::rng();
let mut buf = [0u8; N];
for b in &mut buf {
*b = BASE62_ALPHABET[(rng.next_u32() % 62) as usize];
}
String::from_utf8(buf.to_vec()).unwrap()
}
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BoxID(String);
impl BoxID {
pub const MAX_LENGTH: usize = 128;
pub const SHORT_LENGTH: usize = 8;
pub fn parse(s: &str) -> Option<Self> {
if Self::is_valid(s) {
Some(Self(s.to_string()))
} else {
None
}
}
pub fn is_valid(s: &str) -> bool {
let len = s.len();
if len == 0 || len > Self::MAX_LENGTH {
return false;
}
s.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn short(&self) -> &str {
let n = self.0.len().min(Self::SHORT_LENGTH);
&self.0[..n]
}
pub fn starts_with(&self, prefix: &str) -> bool {
self.0.starts_with(prefix)
}
}
impl fmt::Display for BoxID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for BoxID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BoxID({})", self.short())
}
}
impl AsRef<str> for BoxID {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<str> for BoxID {
fn borrow(&self) -> &str {
&self.0
}
}
impl ToSql for BoxID {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Borrowed(ValueRef::Text(self.0.as_bytes())))
}
}
pub struct BoxIDMint;
impl BoxIDMint {
pub const MINT_LENGTH: usize = 12;
pub fn mint() -> BoxID {
BoxID(mint_base62::<{ Self::MINT_LENGTH }>())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BaseDiskID(String);
impl BaseDiskID {
pub const FULL_LENGTH: usize = 8;
pub const SHORT_LENGTH: usize = 8;
pub fn parse(s: &str) -> Option<Self> {
if Self::is_valid(s) {
Some(Self(s.to_string()))
} else {
None
}
}
pub fn is_valid(s: &str) -> bool {
s.len() == Self::FULL_LENGTH && s.bytes().all(|b| b.is_ascii_alphanumeric())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn short(&self) -> &str {
&self.0[..Self::SHORT_LENGTH]
}
pub fn starts_with(&self, prefix: &str) -> bool {
self.0.starts_with(prefix)
}
}
impl fmt::Display for BaseDiskID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for BaseDiskID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BaseDiskID({})", self.short())
}
}
impl AsRef<str> for BaseDiskID {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<str> for BaseDiskID {
fn borrow(&self) -> &str {
&self.0
}
}
impl ToSql for BaseDiskID {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Borrowed(ValueRef::Text(self.0.as_bytes())))
}
}
pub struct BaseDiskIDMint;
impl BaseDiskIDMint {
pub fn mint() -> BaseDiskID {
BaseDiskID(mint_base62::<{ BaseDiskID::FULL_LENGTH }>())
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use std::collections::HashSet;
#[test]
fn test_mint_length() {
let id = BoxIDMint::mint();
assert_eq!(id.as_str().len(), BoxIDMint::MINT_LENGTH);
}
#[test]
fn test_mint_uniqueness() {
let ids: HashSet<String> = (0..1000)
.map(|_| BoxIDMint::mint().as_str().to_string())
.collect();
assert_eq!(ids.len(), 1000, "all 1000 minted IDs should be unique");
}
#[test]
fn test_mint_alphabet() {
for _ in 0..100 {
let id = BoxIDMint::mint();
for ch in id.as_str().chars() {
assert!(ch.is_ascii_alphanumeric(), "unexpected char: {ch}");
}
}
}
#[test]
fn test_mint_produces_valid_id() {
let id = BoxIDMint::mint();
assert_eq!(id.as_str().len(), BoxIDMint::MINT_LENGTH);
assert!(BoxID::is_valid(id.as_str()));
}
#[test]
fn test_parse_accepts_base62() {
assert!(BoxID::parse("aB3cD4eF5gH6").is_some());
assert!(BoxID::parse("000000000000").is_some());
assert!(BoxID::parse("zzzzzzzzzzzz").is_some());
}
#[test]
fn test_parse_accepts_ulid() {
assert!(BoxID::parse("01HJK4TNRPQSXYZ8WM6NCVT9R1").is_some());
assert!(BoxID::parse("01234567890123456789012345").is_some());
}
#[test]
fn test_parse_accepts_uuid() {
assert!(BoxID::parse("d406c59d-eb09-4bc3-9b3a-62455c7e8f32").is_some());
assert!(BoxID::parse("00000000-0000-0000-0000-000000000000").is_some());
}
#[test]
fn test_parse_accepts_server_prefix_styles() {
assert!(BoxID::parse("sb_abc123XYZ").is_some());
assert!(BoxID::parse("box-2026-tenant-42").is_some());
assert!(BoxID::parse("i-0123456789abcdef").is_some());
assert!(BoxID::parse("a_b-c_1-2").is_some());
}
#[test]
fn test_parse_accepts_boundary_lengths() {
assert!(BoxID::parse("a").is_some(), "single char");
let max = "a".repeat(BoxID::MAX_LENGTH);
assert!(
BoxID::parse(&max).is_some(),
"MAX_LENGTH chars must be accepted"
);
}
#[test]
fn test_parse_rejects_empty_and_oversize() {
assert!(BoxID::parse("").is_none(), "empty");
let too_long = "a".repeat(BoxID::MAX_LENGTH + 1);
assert!(
BoxID::parse(&too_long).is_none(),
"MAX_LENGTH+1 must be rejected"
);
}
#[test]
fn test_parse_rejects_unsafe_characters() {
assert!(BoxID::parse("a/b").is_none(), "slash");
assert!(BoxID::parse("a\\b").is_none(), "backslash");
assert!(BoxID::parse("..").is_none(), "dot-dot");
assert!(BoxID::parse("a.b").is_none(), "dot");
assert!(BoxID::parse(" abc").is_none(), "leading space");
assert!(BoxID::parse("abc\n").is_none(), "trailing newline");
assert!(BoxID::parse("a:b").is_none(), "colon");
assert!(BoxID::parse("a@b").is_none(), "at");
assert!(BoxID::parse("a?b").is_none(), "question");
assert!(BoxID::parse("a%b").is_none(), "percent");
}
#[test]
fn test_short() {
let id = BoxID::parse("aB3cD4eF5gH6").unwrap();
assert_eq!(id.short(), "aB3cD4eF");
assert_eq!(id.short().len(), BoxID::SHORT_LENGTH);
}
#[test]
fn test_short_truncates_long_id_safely() {
let id = BoxID::parse("d406c59d-eb09-4bc3-9b3a-62455c7e8f32").unwrap();
assert_eq!(id.short(), "d406c59d");
assert_eq!(id.short().len(), BoxID::SHORT_LENGTH);
}
#[test]
fn test_short_returns_full_for_short_ids() {
let id = BoxID::parse("abc").unwrap();
assert_eq!(id.short(), "abc");
assert!(id.short().len() <= BoxID::SHORT_LENGTH);
}
#[test]
fn test_display() {
let id = BoxID::parse("aB3cD4eF5gH6").unwrap();
assert_eq!(format!("{id}"), "aB3cD4eF5gH6");
}
#[test]
fn test_debug() {
let id = BoxID::parse("aB3cD4eF5gH6").unwrap();
let debug = format!("{id:?}");
assert_eq!(debug, "BoxID(aB3cD4eF)");
}
#[test]
fn test_starts_with() {
let id = BoxID::parse("aB3cD4eF5gH6").unwrap();
assert!(id.starts_with("aB3"));
assert!(!id.starts_with("xyz"));
}
#[test]
fn test_base_disk_mint_length() {
let id = BaseDiskIDMint::mint();
assert_eq!(id.as_str().len(), BaseDiskID::FULL_LENGTH);
}
#[test]
fn test_base_disk_mint_uniqueness() {
let ids: HashSet<String> = (0..1000)
.map(|_| BaseDiskIDMint::mint().as_str().to_string())
.collect();
assert_eq!(ids.len(), 1000, "all 1000 minted IDs should be unique");
}
#[test]
fn test_base_disk_mint_alphabet() {
for _ in 0..100 {
let id = BaseDiskIDMint::mint();
for ch in id.as_str().chars() {
assert!(ch.is_ascii_alphanumeric(), "unexpected char: {ch}");
}
}
}
#[test]
fn test_base_disk_mint_produces_valid_id() {
let id = BaseDiskIDMint::mint();
assert_eq!(id.as_str().len(), BaseDiskID::FULL_LENGTH);
assert!(BaseDiskID::is_valid(id.as_str()));
}
#[test]
fn test_base_disk_parse_valid() {
assert!(BaseDiskID::parse("aB3cD4eF").is_some());
assert!(BaseDiskID::parse("00000000").is_some());
assert!(BaseDiskID::parse("zzzzzzzz").is_some());
}
#[test]
fn test_base_disk_parse_invalid() {
assert!(BaseDiskID::parse("abc").is_none(), "too short");
assert!(BaseDiskID::parse("aB3cD4eF5").is_none(), "9 chars");
assert!(BaseDiskID::parse("aB3cD4-_").is_none(), "non-alphanumeric");
}
#[test]
fn test_base_disk_short() {
let id = BaseDiskID::parse("aB3cD4eF").unwrap();
assert_eq!(id.short(), "aB3cD4eF");
assert_eq!(id.short().len(), BaseDiskID::SHORT_LENGTH);
}
#[test]
fn test_base_disk_display() {
let id = BaseDiskID::parse("aB3cD4eF").unwrap();
assert_eq!(format!("{id}"), "aB3cD4eF");
}
#[test]
fn test_base_disk_debug() {
let id = BaseDiskID::parse("aB3cD4eF").unwrap();
let debug = format!("{id:?}");
assert_eq!(debug, "BaseDiskID(aB3cD4eF)");
}
#[test]
fn test_base_disk_starts_with() {
let id = BaseDiskID::parse("aB3cD4eF").unwrap();
assert!(id.starts_with("aB3"));
assert!(!id.starts_with("xyz"));
}
proptest! {
#[test]
fn prop_mint_always_valid(_seed in any::<u64>()) {
let id = BoxIDMint::mint();
prop_assert!(BoxID::is_valid(id.as_str()));
prop_assert_eq!(id.as_str().len(), BoxIDMint::MINT_LENGTH);
}
#[test]
fn prop_parse_roundtrip_url_safe(s in "[0-9A-Za-z_-]{1,128}") {
let id = BoxID::parse(&s).unwrap();
prop_assert_eq!(id.as_str(), s.as_str());
}
#[test]
fn prop_parse_rejects_unsafe_bytes(s in ".{1,128}") {
let safe = |b: u8| b.is_ascii_alphanumeric() || b == b'-' || b == b'_';
prop_assume!(s.bytes().any(|b| !safe(b)));
prop_assert!(BoxID::parse(&s).is_none());
}
#[test]
fn prop_parse_rejects_oversize(s in "[0-9A-Za-z_-]{129,200}") {
prop_assert!(BoxID::parse(&s).is_none());
}
#[test]
fn prop_short_is_prefix_of_full(s in "[0-9A-Za-z_-]{1,128}") {
let id = BoxID::parse(&s).unwrap();
prop_assert!(id.as_str().starts_with(id.short()));
prop_assert!(id.short().len() <= BoxID::SHORT_LENGTH);
}
#[test]
fn prop_display_matches_as_str(s in "[0-9A-Za-z_-]{1,128}") {
let id = BoxID::parse(&s).unwrap();
prop_assert_eq!(format!("{id}"), id.as_str());
}
#[test]
fn prop_serde_roundtrip(s in "[0-9A-Za-z_-]{1,128}") {
let id = BoxID::parse(&s).unwrap();
let json = serde_json::to_string(&id).unwrap();
let back: BoxID = serde_json::from_str(&json).unwrap();
prop_assert_eq!(id, back);
}
#[test]
fn prop_base_disk_mint_always_valid(_seed in any::<u64>()) {
let id = BaseDiskIDMint::mint();
prop_assert!(BaseDiskID::is_valid(id.as_str()));
prop_assert_eq!(id.as_str().len(), BaseDiskID::FULL_LENGTH);
}
#[test]
fn prop_base_disk_parse_roundtrip(s in "[0-9A-Za-z]{8}") {
let id = BaseDiskID::parse(&s).unwrap();
prop_assert_eq!(id.as_str(), s.as_str());
}
#[test]
fn prop_base_disk_parse_rejects_wrong_length(s in "[0-9A-Za-z]{1,50}") {
prop_assume!(s.len() != BaseDiskID::FULL_LENGTH);
prop_assert!(BaseDiskID::parse(&s).is_none());
}
#[test]
fn prop_base_disk_parse_rejects_non_alphanumeric(s in ".{8}") {
prop_assume!(s.bytes().any(|b| !b.is_ascii_alphanumeric()));
prop_assert!(BaseDiskID::parse(&s).is_none());
}
#[test]
fn prop_base_disk_display_matches_as_str(s in "[0-9A-Za-z]{8}") {
let id = BaseDiskID::parse(&s).unwrap();
prop_assert_eq!(format!("{id}"), id.as_str());
}
#[test]
fn prop_base_disk_serde_roundtrip(s in "[0-9A-Za-z]{8}") {
let id = BaseDiskID::parse(&s).unwrap();
let json = serde_json::to_string(&id).unwrap();
let back: BaseDiskID = serde_json::from_str(&json).unwrap();
prop_assert_eq!(id, back);
}
}
}