/ src / actor / id.rs
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  }