structured_asn1/memory/text.rs
1//! 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 validated UTF-8 retaining its original capacity charge.
53///
54/// Construction from bytes validates once under their execution before closing
55/// raw mutable access. Construction from a `str` copies its already validated
56/// bytes. Exclusive [`Self::try_push_str`] appends preserve UTF-8 without
57/// revalidating the accumulated prefix.
58/// No reference count, extra allocation or unsafe thread-safety implementation
59/// is introduced. The default owned [`Memory`] form is `Send + Sync`; scoped
60/// forms retain their execution's actual lifetime and thread-safety bounds.
61/// Views are O(1); callers fund their own hashing, equality, formatting and
62/// traversal. [`HashWork`] supports paid [`super::OwnedMap`] lookups by `str`.
63///
64/// ```
65/// use std::sync::Arc;
66/// use structured_asn1::memory::{Memory, MemoryAccount, OwnedText};
67/// let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
68/// let mut bytes = memory.vector();
69/// bytes.try_extend_from_slice("retained 🦀".as_bytes())?;
70/// let text = OwnedText::from_utf8(bytes).unwrap();
71/// assert_eq!(text.as_str(), "retained 🦀");
72/// drop(text);
73/// assert_eq!(memory.used(), 0);
74/// # Ok::<(), structured_asn1::memory::MemoryError>(())
75/// ```
76///
77/// The invariant cannot be invalidated while a string view exists:
78/// ```compile_fail
79/// use structured_asn1::memory::OwnedText;
80/// fn invalidate(text: OwnedText) {
81/// let view = text.as_str();
82/// let mut bytes = text.into_bytes();
83/// bytes[0] = 0xff;
84/// println!("{view}");
85/// }
86/// ```
87pub struct OwnedText<W: Work = Memory> {
88 text: ValidatedText<W>,
89}
90
91impl<W: Work> OwnedText<W> {
92 /// Validates and takes ownership without copying or changing capacity.
93 ///
94 /// Admission pays the full initialized byte length before the first byte is
95 /// examined, against the vector's original local and aggregate ceilings.
96 /// Both failure classes return the unchanged vector through [`TextError`].
97 pub fn from_utf8(bytes: OwnedVec<u8, W>) -> Result<Self, TextError<W>> {
98 ValidatedText::from_utf8(bytes)
99 .map(|text| Self { text })
100 .map_err(|(kind, bytes)| TextError { kind, bytes })
101 }
102
103 /// Copies valid UTF-8 using the existing accounted allocation/copy path.
104 /// No validation scan is needed because the input is already a `str`.
105 pub fn try_from_str(text: &str, execution: &W) -> Result<Self, MemoryError> {
106 Ok(Self {
107 text: ValidatedText::from_str(text, execution)?,
108 })
109 }
110
111 /// Appends valid UTF-8 under this owner's original execution.
112 ///
113 /// Admits copying the suffix and any actual capacity growth before mutation.
114 /// The existing prefix is not validated again; growth alone can require
115 /// moving it. Spare capacity still requires paying for the suffix copy.
116 /// On refusal the original bytes, pointer, capacity and storage charge remain
117 /// intact. Consumed work is not refunded. Empty suffixes require no work.
118 ///
119 /// Existing views must end before this exclusive mutation:
120 /// ```compile_fail
121 /// use structured_asn1::memory::OwnedText;
122 /// fn append(text: &mut OwnedText) {
123 /// let view = text.as_str();
124 /// text.try_push_str("suffix").unwrap();
125 /// println!("{view}");
126 /// }
127 /// ```
128 pub fn try_push_str(&mut self, suffix: &str) -> Result<(), MemoryError> {
129 self.text.try_push_str(suffix)
130 }
131
132 /// Prepares space for at least `additional` more UTF-8 bytes.
133 ///
134 /// Uses the existing vector's exact reservation strategy, without changing
135 /// length, scanning the prefix or adding amortized spare capacity. Actual
136 /// growth and its old/new storage peak use this owner's original execution.
137 /// Refusal preserves initialized bytes, pointer, capacity and storage charge;
138 /// consumed work is not refunded. Sufficient capacity requires no work.
139 pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), MemoryError> {
140 self.text.try_reserve_exact(additional)
141 }
142
143 /// The original account retaining this allocation.
144 #[must_use]
145 pub fn memory(&self) -> &Memory {
146 self.text.bytes().memory()
147 }
148
149 /// The initialized UTF-8 byte length, without traversal.
150 #[must_use]
151 pub fn len(&self) -> usize {
152 self.text.bytes().len()
153 }
154
155 /// Whether the text contains no bytes.
156 #[must_use]
157 pub fn is_empty(&self) -> bool {
158 self.text.bytes().is_empty()
159 }
160
161 /// Allocated byte capacity, including retained spare capacity.
162 #[must_use]
163 pub fn capacity(&self) -> usize {
164 self.text.bytes().capacity()
165 }
166
167 /// Borrows immutable initialized bytes; callers admit subsequent traversal.
168 #[must_use]
169 pub fn as_bytes(&self) -> &[u8] {
170 self.text.bytes()
171 }
172
173 /// Borrows validated immutable UTF-8 in O(1), without scanning or copying.
174 #[must_use]
175 pub fn as_str(&self) -> &str {
176 self.text.as_str()
177 }
178
179 /// Finishes construction by moving the existing buffer's account keeper.
180 /// No copy, validation, allocation, work or capacity change occurs.
181 #[must_use]
182 pub fn into_owned(self) -> OwnedText {
183 OwnedText {
184 text: self.text.into_owned(),
185 }
186 }
187
188 /// Recovers mutable bytes by consuming the text, ending its UTF-8 invariant.
189 /// Existing borrowed views must have ended before this move is possible.
190 #[must_use]
191 pub fn into_bytes(self) -> OwnedVec<u8, W> {
192 self.text.into_bytes()
193 }
194}
195
196impl<W: Work> fmt::Debug for OwnedText<W> {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 self.as_str().fmt(f)
199 }
200}
201
202impl<W: Work> Deref for OwnedText<W> {
203 type Target = str;
204 fn deref(&self) -> &str {
205 self.as_str()
206 }
207}
208
209impl<W: Work> Borrow<str> for OwnedText<W> {
210 fn borrow(&self) -> &str {
211 self.as_str()
212 }
213}
214
215impl<W: Work> AsRef<str> for OwnedText<W> {
216 fn as_ref(&self) -> &str {
217 self.as_str()
218 }
219}
220
221impl<W: Work> fmt::Display for OwnedText<W> {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 f.write_str(self.as_str())
224 }
225}
226
227impl<W: Work, V: Work> PartialEq<OwnedText<V>> for OwnedText<W> {
228 fn eq(&self, other: &OwnedText<V>) -> bool {
229 self.as_str() == other.as_str()
230 }
231}
232
233impl<W: Work> Eq for OwnedText<W> {}
234
235impl<W: Work> Hash for OwnedText<W> {
236 fn hash<H: Hasher>(&self, state: &mut H) {
237 self.as_str().hash(state);
238 }
239}
240
241impl<W: Work> HashWork for OwnedText<W> {
242 fn hash_work(&self) -> Result<u64, MemoryError> {
243 (self.len() as u64)
244 .checked_add(1)
245 .ok_or(MemoryError::WorkExceeded)
246 }
247}