Skip to main content

miden_protocol/account/
access.rs

1use alloc::fmt;
2
3use crate::Felt;
4use crate::errors::RoleSymbolError;
5use crate::utils::ShortCapitalString;
6
7/// Represents a role symbol for role-based access control.
8///
9/// Role symbols can consist of up to 12 uppercase Latin characters and underscores, e.g.
10/// "MINTER", "BURNER", "MINTER_ADMIN".
11///
12/// The label is stored internally as a validated short string (`A`–`Z` and `_`) and can be
13/// converted to a [`Felt`] encoding via [`as_element()`](Self::as_element).
14#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
15pub struct RoleSymbol(ShortCapitalString);
16
17impl RoleSymbol {
18    /// Alphabet used for role symbols (`A-Z` and `_`).
19    pub const ALPHABET: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_";
20
21    /// The minimum integer value of an encoded [`RoleSymbol`].
22    ///
23    /// This value encodes the "A" role symbol.
24    pub const MIN_ENCODED_VALUE: u64 = 1;
25
26    /// The maximum integer value of an encoded [`RoleSymbol`].
27    ///
28    /// This value encodes the "____________" role symbol (12 underscores).
29    pub const MAX_ENCODED_VALUE: u64 = 4052555153018976252;
30
31    /// Constructs a new [`RoleSymbol`] from a string, panicking on invalid input.
32    ///
33    /// # Panics
34    ///
35    /// Panics if:
36    /// - The length of the provided string is less than 1 or greater than 12.
37    /// - The provided role symbol contains characters outside `A-Z` and `_`.
38    pub fn new_unchecked(role_symbol: &str) -> Self {
39        Self::new(role_symbol).expect("invalid role symbol")
40    }
41
42    /// Creates a new [`RoleSymbol`] from the provided role symbol string.
43    ///
44    /// # Errors
45    /// Returns an error if:
46    /// - The length of the provided string is less than 1 or greater than 12.
47    /// - The provided role symbol contains characters outside `A-Z` and `_`.
48    pub fn new(role_symbol: &str) -> Result<Self, RoleSymbolError> {
49        ShortCapitalString::from_ascii_uppercase_and_underscore(role_symbol)
50            .map(Self)
51            .map_err(Into::into)
52    }
53
54    /// Returns the [`Felt`] encoding of this role symbol.
55    pub fn as_element(&self) -> Felt {
56        self.0.as_element(Self::ALPHABET).expect("RoleSymbol alphabet is always valid")
57    }
58}
59
60impl fmt::Display for RoleSymbol {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        self.0.fmt(f)
63    }
64}
65
66impl From<RoleSymbol> for Felt {
67    fn from(role_symbol: RoleSymbol) -> Self {
68        role_symbol.as_element()
69    }
70}
71
72impl From<&RoleSymbol> for Felt {
73    fn from(role_symbol: &RoleSymbol) -> Self {
74        role_symbol.as_element()
75    }
76}
77
78impl TryFrom<&str> for RoleSymbol {
79    type Error = RoleSymbolError;
80
81    fn try_from(role_symbol: &str) -> Result<Self, Self::Error> {
82        Self::new(role_symbol)
83    }
84}
85
86impl TryFrom<Felt> for RoleSymbol {
87    type Error = RoleSymbolError;
88
89    /// Decodes a [`Felt`] representation of the role symbol into a [`RoleSymbol`].
90    fn try_from(felt: Felt) -> Result<Self, Self::Error> {
91        ShortCapitalString::try_from_encoded_felt(
92            felt,
93            Self::ALPHABET,
94            Self::MIN_ENCODED_VALUE,
95            Self::MAX_ENCODED_VALUE,
96        )
97        .map(Self)
98        .map_err(Into::into)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use alloc::string::ToString;
105
106    use assert_matches::assert_matches;
107
108    use super::{Felt, RoleSymbol};
109    use crate::errors::RoleSymbolError;
110
111    #[test]
112    fn test_role_symbol_roundtrip_and_validation() {
113        let role_symbols = ["MINTER", "BURNER", "MINTER_ADMIN", "A", "A_B_C"];
114        for role_symbol in role_symbols {
115            let encoded: Felt = RoleSymbol::new(role_symbol).unwrap().into();
116            let decoded = RoleSymbol::try_from(encoded).unwrap();
117            assert_eq!(decoded.to_string(), role_symbol);
118        }
119
120        assert_matches!(RoleSymbol::new("").unwrap_err(), RoleSymbolError::InvalidLength(0));
121        assert_matches!(
122            RoleSymbol::new("ABCDEFGHIJKLM").unwrap_err(),
123            RoleSymbolError::InvalidLength(13)
124        );
125        assert_matches!(
126            RoleSymbol::new("MINTER-ADMIN").unwrap_err(),
127            RoleSymbolError::InvalidCharacter
128        );
129        assert_matches!(RoleSymbol::new("mINTER").unwrap_err(), RoleSymbolError::InvalidCharacter);
130    }
131}