/ console / program / src / data / identifier / mod.rs
mod.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  mod bytes;
 17  mod equal;
 18  mod from_bits;
 19  mod from_field;
 20  mod parse;
 21  mod serialize;
 22  mod size_in_bits;
 23  mod to_bits;
 24  mod to_field;
 25  
 26  use alphavm_console_network::Network;
 27  use alphavm_console_types::{Field, prelude::*};
 28  
 29  /// An identifier is an **immutable** UTF-8 string,
 30  /// represented as a **constant** field element in the CurrentNetwork.
 31  ///
 32  /// # Requirements
 33  /// The identifier must not be an empty string.
 34  /// The identifier must not start with a number.
 35  /// The identifier must be alphanumeric, and may include underscores.
 36  /// The identifier must not consist solely of underscores.
 37  /// The identifier must fit within the data capacity of a base field element.
 38  #[derive(Copy, Clone)]
 39  pub struct Identifier<N: Network>(Field<N>, u8); // Number of bytes in the identifier.
 40  
 41  impl<N: Network> From<&Identifier<N>> for Identifier<N> {
 42      /// Returns a copy of the identifier.
 43      fn from(identifier: &Identifier<N>) -> Self {
 44          *identifier
 45      }
 46  }
 47  
 48  impl<N: Network> TryFrom<String> for Identifier<N> {
 49      type Error = Error;
 50  
 51      /// Initializes an identifier from a string.
 52      fn try_from(identifier: String) -> Result<Self> {
 53          Self::from_str(&identifier)
 54      }
 55  }
 56  
 57  impl<N: Network> TryFrom<&String> for Identifier<N> {
 58      type Error = Error;
 59  
 60      /// Initializes an identifier from a string.
 61      fn try_from(identifier: &String) -> Result<Self> {
 62          Self::from_str(identifier)
 63      }
 64  }
 65  
 66  impl<N: Network> TryFrom<&str> for Identifier<N> {
 67      type Error = Error;
 68  
 69      /// Initializes an identifier from a string.
 70      fn try_from(identifier: &str) -> Result<Self> {
 71          Self::from_str(identifier)
 72      }
 73  }
 74  
 75  #[cfg(test)]
 76  pub(crate) mod tests {
 77      use super::*;
 78      use alphavm_console_network::MainnetV0;
 79  
 80      type CurrentNetwork = MainnetV0;
 81  
 82      const ITERATIONS: usize = 100;
 83  
 84      /// Samples a random identifier.
 85      pub(crate) fn sample_identifier<N: Network>(rng: &mut TestRng) -> Result<Identifier<N>> {
 86          // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
 87          let string = sample_identifier_as_string::<N>(rng)?;
 88          // Recover the field element from the bits.
 89          let field = Field::<N>::from_bits_le(&string.as_bytes().to_bits_le())?;
 90          // Return the identifier.
 91          Ok(Identifier(field, u8::try_from(string.len()).or_halt_with::<CurrentNetwork>("Invalid identifier length")))
 92      }
 93  
 94      /// Samples a random identifier as a string.
 95      pub(crate) fn sample_identifier_as_string<N: Network>(rng: &mut TestRng) -> Result<String> {
 96          // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
 97          let string = "a".to_string()
 98              + &rng
 99                  .sample_iter(&Alphanumeric)
100                  .take(Field::<N>::size_in_data_bits() / (8 * 2))
101                  .map(char::from)
102                  .collect::<String>();
103          // Ensure identifier fits within the data capacity of the base field.
104          let max_bytes = Field::<N>::size_in_data_bits() / 8; // Note: This intentionally rounds down.
105          match string.len() <= max_bytes {
106              // Return the identifier.
107              true => Ok(string),
108              false => bail!("Identifier exceeds the maximum capacity allowed"),
109          }
110      }
111  
112      /// Samples a random lowercase identifier as a string.
113      pub(crate) fn sample_lowercase_identifier_as_string<N: Network>(rng: &mut TestRng) -> Result<String> {
114          // Sample a random identifier.
115          let string = sample_identifier_as_string::<N>(rng)?;
116          // Return the identifier as lowercase.
117          Ok(string.to_lowercase())
118      }
119  
120      #[test]
121      fn test_try_from() -> Result<()> {
122          let mut rng = TestRng::default();
123  
124          for _ in 0..ITERATIONS {
125              // Sample a random fixed-length alphanumeric string, that always starts with an alphabetic character.
126              let expected_string = sample_identifier_as_string::<CurrentNetwork>(&mut rng)?;
127              // Recover the field element from the bits.
128              let expected_field = Field::<CurrentNetwork>::from_bits_le(&expected_string.as_bytes().to_bits_le())?;
129  
130              // Try to initialize an identifier from the string.
131              let candidate = Identifier::<CurrentNetwork>::try_from(expected_string.as_str())?;
132              assert_eq!(expected_field, candidate.0);
133              assert_eq!(expected_string.len(), candidate.1 as usize);
134          }
135          Ok(())
136      }
137  
138      #[test]
139      fn test_identifier_try_from_illegal() {
140          assert!(Identifier::<CurrentNetwork>::try_from("123").is_err());
141          assert!(Identifier::<CurrentNetwork>::try_from("abc\x08def").is_err());
142          assert!(Identifier::<CurrentNetwork>::try_from("abc\u{202a}def").is_err());
143      }
144  }