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