Skip to main content

structured_asn1/memory/
text.rs

1//! Immutable, independently retained UTF-8 over an existing accounted buffer.
2
3use super::{HashWork, Memory, MemoryError, OwnedVec, Work, allocator::ValidatedText};
4use core::{
5    borrow::Borrow,
6    fmt,
7    hash::{Hash, Hasher},
8    ops::Deref,
9    str::Utf8Error,
10};
11
12/// Why an existing byte buffer could not become immutable UTF-8 text.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum TextErrorKind {
15    /// Work admission refused before UTF-8 validation began.
16    Resource(MemoryError),
17    /// Admitted validation found invalid UTF-8, relative to the original bytes.
18    Utf8(Utf8Error),
19}
20
21/// Failed text construction retaining the unchanged original buffer.
22///
23/// Consumed work is not refunded. Storage remains charged until the recovered
24/// buffer or this error is dropped; failure never copies or reallocates it.
25pub struct TextError<W: Work = Memory> {
26    kind: TextErrorKind,
27    bytes: OwnedVec<u8, W>,
28}
29
30impl<W: Work> TextError<W> {
31    /// The original resource or UTF-8 cause, without inspecting bytes.
32    #[must_use]
33    pub fn kind(&self) -> TextErrorKind {
34        self.kind
35    }
36
37    /// Recovers the original pointer, initialized bytes, capacity and account.
38    #[must_use]
39    pub fn into_bytes(self) -> OwnedVec<u8, W> {
40        self.bytes
41    }
42}
43
44impl<W: Work> fmt::Debug for TextError<W> {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.debug_struct("TextError")
47            .field("kind", &self.kind)
48            .finish_non_exhaustive()
49    }
50}
51
52/// Single-owner immutable UTF-8 retaining its original capacity charge.
53///
54/// Construction from bytes validates once under their execution before closing
55/// mutable access. Construction from a `str` copies its already validated bytes.
56/// No reference count, extra allocation or unsafe thread-safety implementation
57/// is introduced. The default owned [`Memory`] form is `Send + Sync`; scoped
58/// forms retain their execution's actual lifetime and thread-safety bounds.
59/// Views are O(1); callers fund their own hashing, equality, formatting and
60/// traversal. [`HashWork`] supports paid [`super::OwnedMap`] lookups by `str`.
61///
62/// ```
63/// use std::sync::Arc;
64/// use structured_asn1::memory::{Memory, MemoryAccount, OwnedText};
65/// let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
66/// let mut bytes = memory.vector();
67/// bytes.try_extend_from_slice("retained 🦀".as_bytes())?;
68/// let text = OwnedText::from_utf8(bytes).unwrap();
69/// assert_eq!(text.as_str(), "retained 🦀");
70/// drop(text);
71/// assert_eq!(memory.used(), 0);
72/// # Ok::<(), structured_asn1::memory::MemoryError>(())
73/// ```
74///
75/// The invariant cannot be invalidated while a string view exists:
76/// ```compile_fail
77/// use structured_asn1::memory::OwnedText;
78/// fn invalidate(text: OwnedText) {
79///     let view = text.as_str();
80///     let mut bytes = text.into_bytes();
81///     bytes[0] = 0xff;
82///     println!("{view}");
83/// }
84/// ```
85pub struct OwnedText<W: Work = Memory> {
86    text: ValidatedText<W>,
87}
88
89impl<W: Work> OwnedText<W> {
90    /// Validates and takes ownership without copying or changing capacity.
91    ///
92    /// Admission pays the full initialized byte length before the first byte is
93    /// examined, against the vector's original local and aggregate ceilings.
94    /// Both failure classes return the unchanged vector through [`TextError`].
95    pub fn from_utf8(bytes: OwnedVec<u8, W>) -> Result<Self, TextError<W>> {
96        ValidatedText::from_utf8(bytes)
97            .map(|text| Self { text })
98            .map_err(|(kind, bytes)| TextError { kind, bytes })
99    }
100
101    /// Copies valid UTF-8 using the existing accounted allocation/copy path.
102    /// No validation scan is needed because the input is already a `str`.
103    pub fn try_from_str(text: &str, execution: &W) -> Result<Self, MemoryError> {
104        Ok(Self {
105            text: ValidatedText::from_str(text, execution)?,
106        })
107    }
108
109    /// The original account retaining this allocation.
110    #[must_use]
111    pub fn memory(&self) -> &Memory {
112        self.text.bytes().memory()
113    }
114
115    /// The initialized UTF-8 byte length, without traversal.
116    #[must_use]
117    pub fn len(&self) -> usize {
118        self.text.bytes().len()
119    }
120
121    /// Whether the text contains no bytes.
122    #[must_use]
123    pub fn is_empty(&self) -> bool {
124        self.text.bytes().is_empty()
125    }
126
127    /// Allocated byte capacity, including retained spare capacity.
128    #[must_use]
129    pub fn capacity(&self) -> usize {
130        self.text.bytes().capacity()
131    }
132
133    /// Borrows immutable initialized bytes; callers admit subsequent traversal.
134    #[must_use]
135    pub fn as_bytes(&self) -> &[u8] {
136        self.text.bytes()
137    }
138
139    /// Borrows validated immutable UTF-8 in O(1), without scanning or copying.
140    #[must_use]
141    pub fn as_str(&self) -> &str {
142        self.text.as_str()
143    }
144
145    /// Finishes construction by moving the existing buffer's account keeper.
146    /// No copy, validation, allocation, work or capacity change occurs.
147    #[must_use]
148    pub fn into_owned(self) -> OwnedText {
149        OwnedText {
150            text: self.text.into_owned(),
151        }
152    }
153
154    /// Recovers mutable bytes by consuming the text, ending its UTF-8 invariant.
155    /// Existing borrowed views must have ended before this move is possible.
156    #[must_use]
157    pub fn into_bytes(self) -> OwnedVec<u8, W> {
158        self.text.into_bytes()
159    }
160}
161
162impl<W: Work> fmt::Debug for OwnedText<W> {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        self.as_str().fmt(f)
165    }
166}
167
168impl<W: Work> Deref for OwnedText<W> {
169    type Target = str;
170    fn deref(&self) -> &str {
171        self.as_str()
172    }
173}
174
175impl<W: Work> Borrow<str> for OwnedText<W> {
176    fn borrow(&self) -> &str {
177        self.as_str()
178    }
179}
180
181impl<W: Work> AsRef<str> for OwnedText<W> {
182    fn as_ref(&self) -> &str {
183        self.as_str()
184    }
185}
186
187impl<W: Work> fmt::Display for OwnedText<W> {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        f.write_str(self.as_str())
190    }
191}
192
193impl<W: Work, V: Work> PartialEq<OwnedText<V>> for OwnedText<W> {
194    fn eq(&self, other: &OwnedText<V>) -> bool {
195        self.as_str() == other.as_str()
196    }
197}
198
199impl<W: Work> Eq for OwnedText<W> {}
200
201impl<W: Work> Hash for OwnedText<W> {
202    fn hash<H: Hasher>(&self, state: &mut H) {
203        self.as_str().hash(state);
204    }
205}
206
207impl<W: Work> HashWork for OwnedText<W> {
208    fn hash_work(&self) -> Result<u64, MemoryError> {
209        (self.len() as u64)
210            .checked_add(1)
211            .ok_or(MemoryError::WorkExceeded)
212    }
213}