id.rs
1 use candid::CandidType; 2 use ic_stable_structures::{storable::Bound, Storable}; 3 use std::{ 4 borrow::Cow, 5 fmt, 6 hash::{Hash, Hasher}, 7 }; 8 9 const CAPITAL_MAP_SIZE: usize = 3; 10 const MAX_LENGTH_IN_BYTES: usize = 18; 11 const BYTE_LENGTH: usize = CAPITAL_MAP_SIZE + MAX_LENGTH_IN_BYTES; 12 13 /// Human readable id for actor. 14 /// 15 /// ActorIds can contain alphabets, numbers, hyphens and underscores, and are case-insensitive but displayed in case-sensitive format. 16 /// Length of an ActorId must be between 3 and 24 characters. 17 #[derive(Debug, Clone, Copy, CandidType, Default)] 18 pub struct ActorId([u8; BYTE_LENGTH]); // 3 bytes for capital character map and 18 bytes for id 19 20 impl ActorId { 21 /// The maximum length of an ActorId in characters 22 pub const MAX_LENGTH: usize = 24; 23 /// The minimum length of an ActorId in characters 24 pub const MIN_LENGTH: usize = 3; 25 /// The maximum length of an ActorId in bytes 26 pub const MAX_LENGTH_IN_BYTES: usize = MAX_LENGTH_IN_BYTES; 27 /// The minimum length of an ActorId in bytes 28 pub const MIN_LENGTH_IN_BYTES: usize = 3; 29 30 const CAPITAL_MAP_SIZE: usize = CAPITAL_MAP_SIZE; 31 const MIN_LENGTH_IN_BYTES_WITH_CAPITAL_MAP: usize = 32 Self::MIN_LENGTH_IN_BYTES + Self::CAPITAL_MAP_SIZE; 33 const MAX_LENGTH_IN_BYTES_WITH_CAPITAL_MAP: usize = 34 Self::MAX_LENGTH_IN_BYTES + Self::CAPITAL_MAP_SIZE; 35 36 const BITS_PER_CHAR: usize = 6; 37 const CHAR_MASK: u8 = 0b0011_1111; // 6 bits mask 38 39 const ALPHABET_LITERAL_OFFSET: u8 = b'a' - 1; // 1-based index 40 const CAPITAL_LITERAL_OFFSET: u8 = b'A' - 1; // 1-based index 41 const NUMERIC_LITERAL_OFFSET: u8 = b'0' - 1 - 26; // 1-based index + 26 for alphabets 42 43 /// Create a new ActorId from a string 44 pub fn new<S: AsRef<str>>(id_str: S) -> Result<Self, ActorIdError> { 45 Self::from_str_core(id_str.as_ref()) 46 } 47 48 fn from_str_core(s: &str) -> Result<Self, ActorIdError> { 49 let s = s.trim(); 50 let len = s.chars().count(); 51 if len < Self::MIN_LENGTH { 52 return Err(ActorIdError::StringTooShort); 53 } else if len > Self::MAX_LENGTH { 54 return Err(ActorIdError::StringTooLong); 55 } 56 57 let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES_WITH_CAPITAL_MAP]; 58 59 let mut bit_position = 0; 60 61 for (position, c) in s.chars().enumerate() { 62 let value = match c { 63 'a'..='z' => c as u8 - Self::ALPHABET_LITERAL_OFFSET, 64 'A'..='Z' => { 65 let map_byte_index = position / 8; 66 let map_bit_offset = position % 8; 67 unsafe { 68 *bytes.get_unchecked_mut(map_byte_index) |= 1 << map_bit_offset; 69 } 70 71 c as u8 - Self::CAPITAL_LITERAL_OFFSET 72 } 73 '0'..='9' => c as u8 - Self::NUMERIC_LITERAL_OFFSET, 74 '-' => 37, 75 '_' => 38, 76 _ => return Err(ActorIdError::InvalidCharacter(c)), 77 }; 78 79 let byte_index = bit_position / 8 + Self::CAPITAL_MAP_SIZE; 80 let bit_offset = bit_position % 8; 81 82 unsafe { 83 *bytes.get_unchecked_mut(byte_index) |= value << bit_offset; 84 if bit_offset > 2 { 85 *bytes.get_unchecked_mut(byte_index + 1) |= value >> (8 - bit_offset); 86 } 87 } 88 89 bit_position += Self::BITS_PER_CHAR; 90 } 91 92 Ok(Self(bytes)) 93 } 94 95 fn from_slice_core(slice: &[u8]) -> Option<Self> { 96 match slice.len() { 97 len @ Self::MIN_LENGTH_IN_BYTES_WITH_CAPITAL_MAP 98 ..=Self::MAX_LENGTH_IN_BYTES_WITH_CAPITAL_MAP => { 99 let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES_WITH_CAPITAL_MAP]; 100 unsafe { 101 std::ptr::copy_nonoverlapping(slice.as_ptr(), bytes.as_mut_ptr(), len); 102 } 103 Some(Self(bytes)) 104 } 105 _ => None, 106 } 107 } 108 109 /// Create a new ActorId from a byte slice 110 pub fn from_slice(slice: &[u8]) -> Self { 111 match Self::from_slice_core(slice) { 112 Some(id) => id, 113 None => panic!("slice length out of range"), 114 } 115 } 116 117 /// Try to create a new ActorId from a byte slice 118 pub fn try_from_slice(slice: &[u8]) -> Result<Self, ActorIdError> { 119 match Self::from_slice_core(slice) { 120 Some(id) => Ok(id), 121 None => { 122 let len = slice.len(); 123 if len < Self::MIN_LENGTH_IN_BYTES_WITH_CAPITAL_MAP { 124 Err(ActorIdError::BytesTooShort) 125 } else { 126 Err(ActorIdError::BytesTooLong) 127 } 128 } 129 } 130 } 131 132 /// Get the byte representation of the ActorId 133 pub fn as_slice(&self) -> &[u8] { 134 for i in 135 Self::MIN_LENGTH_IN_BYTES_WITH_CAPITAL_MAP..Self::MAX_LENGTH_IN_BYTES_WITH_CAPITAL_MAP 136 { 137 if unsafe { *self.0.get_unchecked(i) } == 0 { 138 return unsafe { self.0.get_unchecked(..i) }; 139 } 140 } 141 142 &self.0 143 } 144 } 145 146 impl PartialEq for ActorId { 147 fn eq(&self, other: &Self) -> bool { 148 self.0[Self::CAPITAL_MAP_SIZE..] == other.0[Self::CAPITAL_MAP_SIZE..] 149 } 150 } 151 152 impl Eq for ActorId {} 153 154 impl Hash for ActorId { 155 fn hash<H: Hasher>(&self, state: &mut H) { 156 self.0[Self::CAPITAL_MAP_SIZE..].hash(state); 157 } 158 } 159 160 impl PartialOrd for ActorId { 161 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 162 Some(self.cmp(other)) 163 } 164 } 165 166 impl Ord for ActorId { 167 fn cmp(&self, other: &Self) -> std::cmp::Ordering { 168 self.0[Self::CAPITAL_MAP_SIZE..].cmp(&other.0[Self::CAPITAL_MAP_SIZE..]) 169 } 170 } 171 172 impl fmt::Display for ActorId { 173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 174 let mut result = String::with_capacity(Self::MAX_LENGTH); 175 let mut bit_position = 0; 176 let mut position = 0; 177 178 while position < Self::MAX_LENGTH { 179 let byte_index = bit_position / 8 + Self::CAPITAL_MAP_SIZE; 180 let bit_offset = bit_position % 8; 181 182 let value = unsafe { 183 if bit_offset <= 2 { 184 (*self.0.get_unchecked(byte_index) >> bit_offset) & Self::CHAR_MASK 185 } else { 186 ((*self.0.get_unchecked(byte_index) >> bit_offset) 187 | (*self.0.get_unchecked(byte_index + 1) << (8 - bit_offset))) 188 & Self::CHAR_MASK 189 } 190 }; 191 192 let char = match value { 193 0 => break, 194 1..=26 => { 195 let map_byte_index = position / 8; 196 let map_bit_offset = position % 8; 197 let is_capital = unsafe { 198 (*self.0.get_unchecked(map_byte_index) >> map_bit_offset) & 1 == 1 199 }; 200 201 if is_capital { 202 (value + Self::CAPITAL_LITERAL_OFFSET) as char 203 } else { 204 (value + Self::ALPHABET_LITERAL_OFFSET) as char 205 } 206 } 207 27..=36 => (value + Self::NUMERIC_LITERAL_OFFSET) as char, 208 37 => '-', 209 38 => '_', 210 _ => unreachable!(), 211 }; 212 213 result.push(char); 214 bit_position += Self::BITS_PER_CHAR; 215 position += 1; 216 } 217 218 f.write_str(&result) 219 } 220 } 221 222 impl std::str::FromStr for ActorId { 223 type Err = ActorIdError; 224 225 /// Parse a string into an ActorId 226 fn from_str(s: &str) -> Result<Self, Self::Err> { 227 Self::from_str_core(s) 228 } 229 } 230 231 impl TryFrom<&str> for ActorId { 232 type Error = ActorIdError; 233 234 /// Try to convert a string into an ActorId 235 fn try_from(value: &str) -> Result<Self, Self::Error> { 236 Self::from_str_core(value) 237 } 238 } 239 240 impl TryFrom<&[u8]> for ActorId { 241 type Error = ActorIdError; 242 243 /// Try to convert a byte slice into an ActorId 244 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { 245 Self::try_from_slice(bytes) 246 } 247 } 248 249 impl TryFrom<Vec<u8>> for ActorId { 250 type Error = ActorIdError; 251 252 /// Try to convert a byte vector into an ActorId 253 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { 254 Self::try_from(bytes.as_slice()) 255 } 256 } 257 258 impl TryFrom<&Vec<u8>> for ActorId { 259 type Error = ActorIdError; 260 261 /// Try to convert a byte vector reference into an ActorId 262 fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> { 263 Self::try_from(bytes.as_slice()) 264 } 265 } 266 267 impl AsRef<[u8]> for ActorId { 268 /// Get the byte representation of the ActorId 269 fn as_ref(&self) -> &[u8] { 270 self.as_slice() 271 } 272 } 273 274 // Serialization 275 impl serde::Serialize for ActorId { 276 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 277 if serializer.is_human_readable() { 278 self.to_string().serialize(serializer) 279 } else { 280 serializer.serialize_bytes(self.as_slice()) 281 } 282 } 283 } 284 285 // Deserialization 286 mod deserialize { 287 use super::ActorId; 288 use std::convert::TryFrom; 289 290 // Simple visitor for deserialization from bytes. We don't support other number types 291 // as there's no need for it. 292 pub(super) struct ActorIdVisitor; 293 294 impl<'de> serde::de::Visitor<'de> for ActorIdVisitor { 295 type Value = super::ActorId; 296 297 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 298 formatter.write_str("bytes or string") 299 } 300 301 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 302 where 303 E: serde::de::Error, 304 { 305 ActorId::from_str_core(v).map_err(E::custom) 306 } 307 308 fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E> 309 where 310 E: serde::de::Error, 311 { 312 ActorId::try_from(value).map_err(E::custom) 313 } 314 } 315 } 316 317 impl<'de> serde::Deserialize<'de> for ActorId { 318 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<ActorId, D::Error> { 319 use serde::de::Error; 320 if deserializer.is_human_readable() { 321 deserializer 322 .deserialize_str(deserialize::ActorIdVisitor) 323 .map_err(D::Error::custom) 324 } else { 325 deserializer 326 .deserialize_bytes(deserialize::ActorIdVisitor) 327 .map_err(D::Error::custom) 328 } 329 } 330 } 331 332 impl Storable for ActorId { 333 fn to_bytes(&self) -> std::borrow::Cow<[u8]> { 334 Cow::Borrowed(self.as_slice()) 335 } 336 337 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self { 338 Self::from_slice(&bytes) 339 } 340 341 const BOUND: Bound = Bound::Bounded { 342 max_size: 21, 343 is_fixed_size: false, 344 }; 345 } 346 347 /// Errors that can occur when working with ActorIds 348 #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] 349 pub enum ActorIdError { 350 #[error("ActorId is longer than {} characters.", ActorId::MAX_LENGTH)] 351 StringTooLong, 352 353 #[error("ActorId is shorter than {} characters.", ActorId::MIN_LENGTH)] 354 StringTooShort, 355 356 #[error("Bytes is longer than {} bytes.", ActorId::MAX_LENGTH_IN_BYTES)] 357 BytesTooLong, 358 359 #[error("Bytes is shorter than {} bytes.", ActorId::MIN_LENGTH_IN_BYTES)] 360 BytesTooShort, 361 362 #[error("InvalActorId character '{0}' in ActorId.")] 363 InvalidCharacter(char), 364 } 365 366 #[cfg(test)] 367 mod tests { 368 use super::*; 369 370 #[test] 371 fn test_actor_id_new() { 372 let id = ActorId::new("test").unwrap(); 373 assert_eq!(id.to_string(), "test"); 374 375 let id = ActorId::new("Anthol_User").unwrap(); 376 assert_eq!(id.to_string(), "Anthol_User"); 377 378 let id = ActorId::new("Anthol_User-123").unwrap(); 379 assert_eq!(id.to_string(), "Anthol_User-123"); 380 381 let id = ActorId::new("AntholUser_").unwrap(); 382 assert_eq!(id.to_string(), "AntholUser_"); 383 384 let id = ActorId::new("z".repeat(24).as_str()).unwrap(); 385 assert_eq!(id.to_string(), "z".repeat(24)); 386 387 assert_eq!(ActorId::new("id"), Err(ActorIdError::StringTooShort)); 388 389 assert_eq!( 390 ActorId::new("z".repeat(25).as_str()), 391 Err(ActorIdError::StringTooLong) 392 ); 393 394 assert_eq!( 395 ActorId::new("id!"), 396 Err(ActorIdError::InvalidCharacter('!')) 397 ); 398 399 assert_eq!( 400 ActorId::new("アイディー"), 401 Err(ActorIdError::InvalidCharacter('ア')) 402 ); 403 } 404 405 #[test] 406 fn test_id_from_str() { 407 use std::str::FromStr; 408 409 let id = ActorId::from_str("Anthol_User").unwrap(); 410 assert_eq!(id.to_string(), "Anthol_User"); 411 412 let id = ActorId::from_str("Anthol_User-123").unwrap(); 413 assert_eq!(id.to_string(), "Anthol_User-123"); 414 } 415 416 #[test] 417 fn test_id_try_from_slice() { 418 let id = ActorId::new("Anthol_User").unwrap(); 419 let bytes = id.as_slice(); 420 let id2 = ActorId::try_from(bytes).unwrap(); 421 assert_eq!(id, id2); 422 assert_eq!(id.to_string(), id2.to_string()); 423 } 424 425 #[test] 426 fn test_id_equality() { 427 let id1 = ActorId::new("Anthol_User").unwrap(); 428 let id2 = ActorId::new("anthol_user").unwrap(); 429 assert_eq!(id1, id2); 430 431 let id1 = ActorId::new("Anthol_User").unwrap(); 432 let id2 = ActorId::new("Anthol_User-123").unwrap(); 433 assert_ne!(id1, id2); 434 } 435 436 #[test] 437 fn test_id_ordering() { 438 let id1 = ActorId::new("Anthol_User").unwrap(); 439 let id2 = ActorId::new("anthol_user").unwrap(); 440 assert_eq!(id1, id2); 441 442 let id1 = ActorId::new("Anthol_User").unwrap(); 443 let id2 = ActorId::new("Anthol_User-123").unwrap(); 444 assert!(id1 < id2); 445 } 446 447 #[test] 448 fn test_id_serde() { 449 let id = ActorId::new("Anthol_User").unwrap(); 450 let json = serde_json::to_string(&id).unwrap(); 451 assert_eq!(json, "\"Anthol_User\""); 452 453 let id_json: ActorId = serde_json::from_str(&json).unwrap(); 454 assert_eq!(id, id_json); 455 assert_eq!(id.to_string(), id_json.to_string()); 456 457 let bin = bincode::serialize(&id).unwrap(); 458 let id_bincode: ActorId = bincode::deserialize(&bin).unwrap(); 459 assert_eq!(id, id_bincode); 460 assert_eq!(id.to_string(), id_bincode.to_string()); 461 } 462 }