use std::fmt;
use crate::{error::CustomAlphabetError, FLICKR_BASE_58};
pub struct BaseConverter {
alphabet: &'static str,
}
impl Default for BaseConverter {
fn default() -> Self {
Self {
alphabet: FLICKR_BASE_58,
}
}
}
impl BaseConverter {
pub fn new_custom(alphabet: &'static str) -> Result<Self, CustomAlphabetError> {
let converter = Self { alphabet };
converter.validate()?;
Ok(converter)
}
pub fn validate(&self) -> Result<(), CustomAlphabetError> {
let trimmed = self.alphabet.trim();
if trimmed.is_empty() {
return Err(CustomAlphabetError::EmptyAlphabet);
}
if trimmed.len() == 1 {
return Err(CustomAlphabetError::Length);
}
let has_duplicates = trimmed
.chars()
.any(|c| trimmed.chars().filter(|&x| x == c).count() > 1);
if has_duplicates {
return Err(CustomAlphabetError::DuplicateAlphabetCharacter);
}
Ok(())
}
pub fn convert(&self, uuid_string: &str) -> Result<Vec<u8>, DecodeHexError> {
let decoded_bytes = decode_hex(&uuid_string)?;
let alphabet_length = get_short_id_length(self.alphabet.len() as f64);
let result_bytes = bytes_to_custom_bytes(
&decoded_bytes,
self.alphabet.as_bytes(),
alphabet_length,
self.alphabet.chars().next().unwrap(),
);
Ok(result_bytes)
}
pub fn convert_to_hex(&self, target_bytes: &[u8]) -> Result<String, InvalidCustomBase> {
let regular_bytes = custom_bytes_to_bytes(target_bytes, self.alphabet.as_bytes())?;
let hex_string = encode_hex(®ular_bytes);
let padded = pad_start(&hex_string, 32, '0');
Ok(padded)
}
}
#[derive(Debug)]
pub enum DecodeHexError {
InvalidLength,
InvalidCharacter,
}
impl fmt::Display for DecodeHexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodeHexError::InvalidLength => write!(f, "Invalid hexadecimal string length"),
DecodeHexError::InvalidCharacter => {
write!(f, "Invalid character in hexadecimal string")
}
}
}
}
fn decode_hex(hex_string: &str) -> Result<Vec<u8>, DecodeHexError> {
let hex_chars: Vec<char> = hex_string.chars().collect();
let mut result = Vec::new();
if hex_chars.len() % 2 != 0 {
return Err(DecodeHexError::InvalidLength);
}
for i in (0..hex_chars.len()).step_by(2) {
let first_digit = hex_chars[i].to_digit(16);
let second_digit = hex_chars[i + 1].to_digit(16);
match (first_digit, second_digit) {
(Some(first), Some(second)) => {
result.push((first << 4 | second) as u8);
}
_ => {
return Err(DecodeHexError::InvalidCharacter);
}
}
}
Ok(result)
}
fn bytes_to_custom_bytes(
bytes: &[u8],
alphabet: &[u8],
target_length: usize,
padding_char: char,
) -> Vec<u8> {
let base = alphabet.len() as u128;
let mut result = Vec::new();
let mut value = 0u128;
for &byte in bytes {
value = value * 256 + byte as u128;
}
while value > 0 {
let index = (value % base) as usize;
result.push(alphabet[index]);
value /= base;
}
result.reverse();
while result.len() < target_length {
result.insert(0, padding_char as u8);
}
result
}
#[derive(Debug, Clone)]
pub struct InvalidCustomBase;
fn custom_bytes_to_bytes(
encoded_bytes: &[u8],
alphabet: &[u8],
) -> Result<Vec<u8>, InvalidCustomBase> {
let base = alphabet.len() as u128;
let mut result = 0u128;
for &byte in encoded_bytes {
let index = alphabet.iter().position(|&c| c == byte);
match index {
Some(i) => {
result = result.checked_mul(base).ok_or(InvalidCustomBase)? + i as u128;
}
None => return Err(InvalidCustomBase),
}
}
let mut decoded_bytes = Vec::new();
while result > 0 {
decoded_bytes.push((result % 256) as u8);
result /= 256;
}
decoded_bytes.reverse(); Ok(decoded_bytes)
}
fn encode_hex(bytes: &[u8]) -> String {
let hex_chars: Vec<String> = bytes.iter().map(|b| format!("{:02x}", b)).collect();
hex_chars.join("")
}
fn pad_start(input: &str, target_length: usize, padding: char) -> String {
if input.len() >= target_length {
return input.to_string();
}
let padding_length = target_length - input.len();
let padded_string: String = std::iter::repeat(padding).take(padding_length).collect();
format!("{}{}", padded_string, input)
}
fn get_short_id_length(alphabet_length: f64) -> usize {
((2.0_f64.powi(128)).log(alphabet_length).ceil()) as usize
}