aranya_policy_text/
text.rs

1use alloc::string::String;
2use core::{
3    ffi::CStr,
4    fmt,
5    ops::{Add, Deref},
6    str::FromStr,
7};
8
9use serde::de;
10
11use crate::{
12    error::{InvalidText, InvalidTextRepr},
13    repr::Repr,
14};
15
16/// A string-like value which is utf8 without nul bytes.
17#[derive(
18    Clone,
19    Default,
20    PartialEq,
21    Eq,
22    Hash,
23    PartialOrd,
24    Ord,
25    rkyv::Archive,
26    rkyv::Serialize,
27    rkyv::Deserialize,
28)]
29#[rkyv(bytecheck(verify))]
30#[rkyv(derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord))]
31pub struct Text(pub(crate) Repr);
32
33/// Creates a `Text` from a string literal.
34///
35/// Fails at compile time for invalid values.
36#[macro_export]
37macro_rules! text {
38    () => {
39        $crate::Text::new()
40    };
41    ($($e:tt)+) => {
42        // SAFETY: `validate_text` validates `Text`'s requirements.
43        unsafe {
44            $crate::Text::__from_literal($crate::__hidden::validate_text!($($e)+))
45        }
46    };
47}
48
49impl Text {
50    pub(crate) fn validate(s: &str) -> Result<(), InvalidText> {
51        if let Some(index) = s.bytes().position(|b| b == 0) {
52            return Err(InvalidText(InvalidTextRepr::ContainsNul { index }));
53        }
54        Ok(())
55    }
56
57    /// Creates an empty text.
58    pub const fn new() -> Self {
59        Self(Repr::empty())
60    }
61
62    /// SAFETY: The string must meet `Text`'s requirements.
63    #[doc(hidden)]
64    pub const unsafe fn __from_literal(lit: &'static str) -> Self {
65        Self(Repr::from_static(lit))
66    }
67
68    /// Compare two text values for equality.
69    ///
70    /// Like `Eq` but `const`.
71    pub const fn const_eq(&self, other: &Self) -> bool {
72        let lhs = self.0.as_str().as_bytes();
73        let rhs = other.0.as_str().as_bytes();
74        if lhs.len() != rhs.len() {
75            return false;
76        }
77        let mut i = 0;
78        while i < lhs.len() && i < rhs.len() {
79            if lhs[i] != rhs[i] {
80                return false;
81            }
82            // Cannot overflow or wrap since `i` is
83            // `usize` and `<[_]>::len()` is at most
84            // `isize::MAX`.
85            #[allow(clippy::arithmetic_side_effects)]
86            {
87                i += 1;
88            }
89        }
90        true
91    }
92
93    /// Extracts a string slice containing the entire text.
94    pub const fn as_str(&self) -> &str {
95        self.0.as_str()
96    }
97}
98
99impl fmt::Display for Text {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        self.0.as_str().fmt(f)
102    }
103}
104
105impl fmt::Debug for Text {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        self.0.as_str().fmt(f)
108    }
109}
110
111impl PartialEq<str> for Text {
112    fn eq(&self, other: &str) -> bool {
113        self.0.as_str().eq(other)
114    }
115}
116impl PartialEq<&str> for Text {
117    fn eq(&self, other: &&str) -> bool {
118        self.0.as_str().eq(*other)
119    }
120}
121
122impl FromStr for Text {
123    type Err = InvalidText;
124    fn from_str(value: &str) -> Result<Self, Self::Err> {
125        Self::validate(value)?;
126        Ok(Self(Repr::from_str(value)))
127    }
128}
129
130impl TryFrom<String> for Text {
131    type Error = InvalidText;
132    fn try_from(value: String) -> Result<Self, Self::Error> {
133        value.as_str().parse()
134    }
135}
136
137impl TryFrom<&CStr> for Text {
138    type Error = core::str::Utf8Error;
139    fn try_from(value: &CStr) -> Result<Self, Self::Error> {
140        let s: &str = value.to_str()?;
141        // NB: CStr cannot contain nul.
142        Ok(Self(Repr::from_str(s)))
143    }
144}
145
146impl Add for &Text {
147    type Output = Text;
148    fn add(self, rhs: Self) -> Self::Output {
149        let mut s = String::from(self.0.as_str());
150        s.push_str(rhs.as_str());
151        debug_assert!(
152            Text::validate(&s).is_ok(),
153            "text should stay valid under concatenation"
154        );
155        Text(Repr::from_str(&s))
156    }
157}
158
159impl serde::Serialize for Text {
160    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161    where
162        S: serde::Serializer,
163    {
164        self.0.serialize(serializer)
165    }
166}
167
168impl<'de> serde::Deserialize<'de> for Text {
169    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170    where
171        D: serde::Deserializer<'de>,
172    {
173        let r = Repr::deserialize(deserializer)?;
174        Self::validate(r.as_str()).map_err(|_| {
175            de::Error::invalid_value(de::Unexpected::Str(r.as_str()), &"no nul bytes")
176        })?;
177        Ok(Self(r))
178    }
179}
180
181impl Deref for Text {
182    type Target = str;
183
184    fn deref(&self) -> &Self::Target {
185        self.as_str()
186    }
187}
188
189impl<T> AsRef<T> for Text
190where
191    T: ?Sized,
192    <Self as Deref>::Target: AsRef<T>,
193{
194    fn as_ref(&self) -> &T {
195        self.deref().as_ref()
196    }
197}
198
199impl ArchivedText {
200    pub fn as_str(&self) -> &str {
201        self.0.as_str()
202    }
203}
204
205// SAFETY: This impl validates the string as a `Text`.
206unsafe impl<C> rkyv::bytecheck::Verify<C> for ArchivedText
207where
208    C: rkyv::rancor::Fallible<Error: rkyv::rancor::Source> + ?Sized,
209{
210    fn verify(&self, _context: &mut C) -> Result<(), <C as rkyv::rancor::Fallible>::Error> {
211        Text::validate(self.as_str()).map_err(rkyv::rancor::Source::new)
212    }
213}