Skip to main content

calimero_primitives/
alias.rs

1#[cfg(test)]
2#[path = "tests/alias.rs"]
3mod tests;
4
5use core::cmp::Ordering as CmpOrdering;
6use core::hash::{Hash, Hasher};
7use core::marker::PhantomData;
8use core::str::{from_utf8, from_utf8_unchecked, FromStr};
9use std::fmt;
10
11use serde::{de, ser, Deserialize, Serialize};
12use thiserror::Error;
13
14use crate::application::ApplicationId;
15use crate::context::ContextId;
16use crate::identity::PublicKey;
17
18const MAX_ALIAS_LEN: usize = 50;
19
20// Compile time assertion to ensure that the `MAX_ALIAS_LEN` fits within `u8`.
21const _: () = {
22    // Assert that MAX_ALIAS_LEN can fit within an 8-bit unsigned integer (0-255).
23    // This panic will trigger a compilation error with the provided message.
24    assert!(
25        !(MAX_ALIAS_LEN > u8::MAX as usize),
26        "MAX_ALIAS_LEN must be a value that fits in 8 bits."
27    );
28};
29
30pub trait ScopedAlias {
31    type Scope;
32}
33
34impl ScopedAlias for ContextId {
35    type Scope = ();
36}
37
38impl ScopedAlias for PublicKey {
39    type Scope = ContextId;
40}
41
42impl ScopedAlias for ApplicationId {
43    type Scope = ();
44}
45
46pub struct Alias<T> {
47    str: [u8; MAX_ALIAS_LEN],
48    len: u8,
49    _pd: PhantomData<T>,
50}
51
52impl<T> Copy for Alias<T> {}
53impl<T> Clone for Alias<T> {
54    fn clone(&self) -> Self {
55        *self
56    }
57}
58
59#[derive(Copy, Clone, Debug, Error)]
60#[error("invalid alias: {}")]
61pub enum InvalidAlias {
62    #[error("exceeds maximum length of {} characters", MAX_ALIAS_LEN)]
63    TooLong,
64}
65
66impl<T> Alias<T> {
67    /// Creates a new Alias from a string slice.
68    ///
69    /// # Panics
70    ///
71    /// Panics if the string exceeds `MAX_ALIAS_LEN`.
72    #[must_use]
73    pub fn new(s: &str) -> Self {
74        s.parse().expect("alias too long")
75    }
76
77    /// Creates a new Alias from a string slice.
78    pub fn try_from_str(s: &str) -> Result<Self, InvalidAlias> {
79        s.parse()
80    }
81
82    #[must_use]
83    pub fn as_str(&self) -> &str {
84        let bytes = &self.str[..self.len as usize];
85        unsafe { from_utf8_unchecked(bytes) }
86    }
87}
88
89impl<T> AsRef<str> for Alias<T> {
90    fn as_ref(&self) -> &str {
91        self.as_str()
92    }
93}
94
95impl<T> FromStr for Alias<T> {
96    type Err = InvalidAlias;
97
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        if s.len() > MAX_ALIAS_LEN {
100            return Err(InvalidAlias::TooLong);
101        }
102
103        let mut str = [0; MAX_ALIAS_LEN];
104        str[..s.len()].copy_from_slice(s.as_bytes());
105
106        // NOTE: This conversion should never return an error because the assert above
107        // ensures `s.len()` is less than `MAX_ALIAS_LEN` (50), which always fits in a u8.
108        let len_u8 = u8::try_from(s.len()).map_err(|_| InvalidAlias::TooLong)?;
109
110        Ok(Self {
111            str,
112            len: len_u8,
113            _pd: PhantomData,
114        })
115    }
116}
117
118impl<T> fmt::Display for Alias<T> {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.pad(self.as_str())
121    }
122}
123
124impl<T> fmt::Debug for Alias<T> {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        f.debug_tuple("Alias").field(&self.as_str()).finish()
127    }
128}
129
130impl<T> Eq for Alias<T> {}
131
132impl<T> PartialEq for Alias<T> {
133    fn eq(&self, other: &Self) -> bool {
134        self.str == other.str
135    }
136}
137
138impl<T> Ord for Alias<T> {
139    fn cmp(&self, other: &Self) -> CmpOrdering {
140        self.str.cmp(&other.str)
141    }
142}
143
144impl<T> PartialOrd for Alias<T> {
145    fn partial_cmp(&self, other: &Self) -> Option<CmpOrdering> {
146        Some(self.cmp(other))
147    }
148}
149
150impl<T> Serialize for Alias<T> {
151    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
152    where
153        S: ser::Serializer,
154    {
155        serializer.serialize_str(self.as_str())
156    }
157}
158
159impl<'de, T> Deserialize<'de> for Alias<T> {
160    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161    where
162        D: de::Deserializer<'de>,
163    {
164        struct AliasVisitor<T>(PhantomData<T>);
165
166        impl<T> de::Visitor<'_> for AliasVisitor<T> {
167            type Value = Alias<T>;
168
169            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170                write!(f, "an alias of at most {MAX_ALIAS_LEN} characters")
171            }
172
173            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
174            where
175                E: de::Error,
176            {
177                Alias::from_str(v).map_err(de::Error::custom)
178            }
179
180            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
181            where
182                E: de::Error,
183            {
184                let Ok(s) = from_utf8(v) else {
185                    return Err(de::Error::invalid_value(de::Unexpected::Bytes(v), &self));
186                };
187
188                Alias::from_str(s).map_err(de::Error::custom)
189            }
190        }
191
192        deserializer.deserialize_str(AliasVisitor(PhantomData))
193    }
194}
195
196impl<T> Hash for Alias<T> {
197    fn hash<H: Hasher>(&self, state: &mut H) {
198        self.as_str().hash(state);
199    }
200}