use alloc::fmt;
use alloc::string::String;
use crate::Felt;
use crate::errors::ShortCapitalStringError;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct ShortCapitalString(String);
impl ShortCapitalString {
pub const MAX_LENGTH: usize = 12;
pub fn from_ascii_uppercase(
string: impl Into<String>,
) -> Result<Self, ShortCapitalStringError> {
let string = string.into();
let char_count = string.chars().count();
if char_count == 0 || char_count > Self::MAX_LENGTH {
return Err(ShortCapitalStringError::InvalidLength(char_count));
}
for character in string.chars() {
if !character.is_ascii_uppercase() {
return Err(ShortCapitalStringError::InvalidCharacter);
}
}
Ok(Self(string))
}
pub fn from_ascii_uppercase_and_underscore(
string: impl Into<String>,
) -> Result<Self, ShortCapitalStringError> {
let string = string.into();
let char_count = string.chars().count();
if char_count == 0 || char_count > Self::MAX_LENGTH {
return Err(ShortCapitalStringError::InvalidLength(char_count));
}
for character in string.chars() {
if !character.is_ascii_uppercase() && character != '_' {
return Err(ShortCapitalStringError::InvalidCharacter);
}
}
Ok(Self(string))
}
pub fn as_element(&self, alphabet: &str) -> Result<Felt, ShortCapitalStringError> {
debug_assert!(
alphabet.is_ascii(),
"ShortCapitalString::as_element: alphabet must be ASCII-only"
);
let alphabet_len = alphabet.len() as u64;
let mut encoded_value: u64 = 0;
for character in self.0.chars() {
let digit = alphabet
.chars()
.position(|c| c == character)
.map(|pos| pos as u64)
.ok_or(ShortCapitalStringError::InvalidCharacter)?;
encoded_value = encoded_value * alphabet_len + digit;
}
let char_len = self.0.chars().count() as u64;
encoded_value = encoded_value * alphabet_len + char_len;
Ok(Felt::new_unchecked(encoded_value))
}
pub fn try_from_encoded_felt(
encoded_string: Felt,
alphabet: &str,
min_encoded_value: u64,
max_encoded_value: u64,
) -> Result<Self, ShortCapitalStringError> {
let encoded_value = encoded_string.as_canonical_u64();
if encoded_value < min_encoded_value {
return Err(ShortCapitalStringError::ValueTooSmall(encoded_value));
}
if encoded_value > max_encoded_value {
return Err(ShortCapitalStringError::ValueTooLarge(encoded_value));
}
debug_assert!(
alphabet.is_ascii(),
"ShortCapitalString::try_from_encoded_felt: alphabet must be ASCII-only"
);
let alphabet_len = alphabet.len() as u64;
let mut remaining_value = encoded_value;
let string_len = (remaining_value % alphabet_len) as usize;
if string_len == 0 || string_len > Self::MAX_LENGTH {
return Err(ShortCapitalStringError::InvalidLength(string_len));
}
remaining_value /= alphabet_len;
let mut decoded = String::with_capacity(string_len);
for _ in 0..string_len {
let digit = (remaining_value % alphabet_len) as usize;
let character =
alphabet.chars().nth(digit).ok_or(ShortCapitalStringError::InvalidCharacter)?;
decoded.insert(0, character);
remaining_value /= alphabet_len;
}
if remaining_value != 0 {
return Err(ShortCapitalStringError::DataNotFullyDecoded);
}
Ok(Self(decoded))
}
}
impl fmt::Display for ShortCapitalString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(test)]
mod tests {
use alloc::string::{String, ToString};
use assert_matches::assert_matches;
use super::{Felt, ShortCapitalString};
use crate::errors::ShortCapitalStringError;
#[test]
fn short_capital_string_encode_decode_roundtrip() {
let short_string = ShortCapitalString::from_ascii_uppercase("MIDEN").unwrap();
let encoded = short_string.as_element("ABCDEFGHIJKLMNOPQRSTUVWXYZ").unwrap();
let decoded = ShortCapitalString::try_from_encoded_felt(
encoded,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
1,
2481152873203736562,
)
.unwrap();
assert_eq!(decoded.to_string(), "MIDEN");
let name = String::from("MIDEN");
let from_name = ShortCapitalString::from_ascii_uppercase(name).unwrap();
assert_eq!(from_name.to_string(), "MIDEN");
}
#[test]
fn short_capital_string_rejects_invalid_values() {
assert_matches!(
ShortCapitalString::from_ascii_uppercase("").unwrap_err(),
ShortCapitalStringError::InvalidLength(0)
);
assert_matches!(
ShortCapitalString::from_ascii_uppercase("ABCDEFGHIJKLM").unwrap_err(),
ShortCapitalStringError::InvalidLength(13)
);
assert_matches!(
ShortCapitalString::from_ascii_uppercase("A_B").unwrap_err(),
ShortCapitalStringError::InvalidCharacter
);
assert_matches!(
ShortCapitalString::from_ascii_uppercase_and_underscore("MINTER-ADMIN").unwrap_err(),
ShortCapitalStringError::InvalidCharacter
);
let err = ShortCapitalString::try_from_encoded_felt(
Felt::ZERO,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
1,
2481152873203736562,
)
.unwrap_err();
assert_matches!(err, ShortCapitalStringError::ValueTooSmall(0));
}
}