use super::base36::{CAPACITY_6, decode, encode_padded, is_valid_sid, required_length};
use super::error::{Result, SidError};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct SmartId {
#[serde(default)]
global_id: u64,
#[serde(default = "default_length")]
length: usize,
}
fn default_length() -> usize {
4
}
impl SmartId {
pub fn from_global_id(global_id: u64) -> Result<Self> {
if global_id >= CAPACITY_6 {
return Err(SidError::GlobalIdOverflow(global_id));
}
let length = required_length(global_id);
Ok(Self { global_id, length })
}
pub fn from_global_id_with_length(global_id: u64, min_length: usize) -> Result<Self> {
if global_id >= CAPACITY_6 {
return Err(SidError::GlobalIdOverflow(global_id));
}
let required = required_length(global_id);
let length = min_length.max(required).min(6);
Ok(Self { global_id, length })
}
pub fn compute(
local_counter: u64,
contributor_id: u16,
num_contributors: u16,
min_length: usize,
) -> Result<Self> {
if contributor_id >= 999 {
return Err(SidError::ContributorIdTooLarge(contributor_id));
}
let global_id = local_counter * (num_contributors as u64) + (contributor_id as u64) + 1;
Self::from_global_id_with_length(global_id, min_length)
}
pub fn global_id(&self) -> u64 {
self.global_id
}
pub fn length(&self) -> usize {
self.length
}
pub fn as_str(&self) -> String {
encode_padded(self.global_id, self.length)
}
pub fn parse(s: &str) -> Result<Self> {
if !is_valid_sid(s) {
return Err(SidError::InvalidFormat(s.to_string()));
}
let global_id = decode(s)?;
Ok(Self {
global_id,
length: s.len(),
})
}
}
impl fmt::Display for SmartId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for SmartId {
type Err = SidError;
fn from_str(s: &str) -> Result<Self> {
Self::parse(s)
}
}
impl PartialOrd for SmartId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SmartId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.global_id.cmp(&other.global_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_global_id() {
let sid = SmartId::from_global_id(0).unwrap();
assert_eq!(sid.global_id(), 0);
assert_eq!(sid.length(), 4);
assert_eq!(sid.as_str(), "0000");
let sid = SmartId::from_global_id(12).unwrap();
assert_eq!(sid.as_str(), "000c");
let sid = SmartId::from_global_id(1679615).unwrap();
assert_eq!(sid.as_str(), "zzzz");
assert_eq!(sid.length(), 4);
let sid = SmartId::from_global_id(1679616).unwrap();
assert_eq!(sid.as_str(), "10000");
assert_eq!(sid.length(), 5);
}
#[test]
fn test_compute() {
let sid = SmartId::compute(0, 0, 1, 4).unwrap();
assert_eq!(sid.global_id(), 1);
assert_eq!(sid.as_str(), "0001");
let sid = SmartId::compute(12, 0, 1, 4).unwrap();
assert_eq!(sid.global_id(), 13); assert_eq!(sid.as_str(), "000d");
let sid = SmartId::compute(12, 0, 999, 4).unwrap();
assert_eq!(sid.global_id(), 12 * 999 + 0 + 1);
assert_eq!(sid.global_id(), 11989);
let sid = SmartId::compute(12, 1, 999, 4).unwrap();
assert_eq!(sid.global_id(), 12 * 999 + 1 + 1);
assert_eq!(sid.global_id(), 11990);
let sid = SmartId::compute(12, 998, 999, 4).unwrap();
assert_eq!(sid.global_id(), 12 * 999 + 998 + 1);
assert_eq!(sid.global_id(), 12987);
}
#[test]
fn test_parse() {
let sid = SmartId::parse("0000").unwrap();
assert_eq!(sid.global_id(), 0);
assert_eq!(sid.length(), 4);
let sid = SmartId::parse("0990").unwrap();
assert_eq!(sid.global_id(), 11988);
let sid = SmartId::parse("zzzz").unwrap();
assert_eq!(sid.global_id(), 1679615);
let sid = SmartId::parse("10000").unwrap();
assert_eq!(sid.global_id(), 1679616);
assert_eq!(sid.length(), 5);
}
#[test]
fn test_parse_invalid() {
assert!(SmartId::parse("abc").is_err()); assert!(SmartId::parse("abcdefg").is_err()); assert!(SmartId::parse("ABCD").is_err()); assert!(SmartId::parse("ab-d").is_err()); }
#[test]
fn test_roundtrip() {
for global_id in [0, 12, 35, 1000, 11988, 1679615, 1679616, 60466175] {
let sid = SmartId::from_global_id(global_id).unwrap();
let s = sid.as_str();
let parsed = SmartId::parse(&s).unwrap();
assert_eq!(parsed.global_id(), global_id);
}
}
#[test]
fn test_display() {
let sid = SmartId::from_global_id(12).unwrap();
assert_eq!(format!("{}", sid), "000c");
}
#[test]
fn test_from_str() {
let sid: SmartId = "0990".parse().unwrap();
assert_eq!(sid.global_id(), 11988);
}
#[test]
fn test_ordering() {
let a = SmartId::from_global_id(100).unwrap();
let b = SmartId::from_global_id(200).unwrap();
let c = SmartId::from_global_id(100).unwrap();
assert!(a < b);
assert!(b > a);
assert_eq!(a, c);
}
#[test]
fn test_serde() {
let sid = SmartId::from_global_id(11988).unwrap();
let json = serde_json::to_string(&sid).unwrap();
let parsed: SmartId = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, sid);
}
#[test]
fn test_contributor_id_too_large() {
let result = SmartId::compute(0, 999, 1000, 4);
assert!(matches!(result, Err(SidError::ContributorIdTooLarge(999))));
}
}