1use core::{borrow::Borrow, ops::RangeBounds, str};
2
3use super::{
4 buffer::Buffer,
5 error::*,
6 utf8_buf::{Utf8Buf, Utf8BufMut, range_bounds_to_start_end, sealed, validate_char_boundary},
7 utf8_drain::Utf8Drain,
8 utf8_edit::{RawUtf8Storage, RetainGuard, replace_bytes as raw_replace_bytes},
9};
10
11mod cmp;
12mod fmt;
13mod from;
14mod ops;
15
16#[cfg(feature = "arbitrary")]
17mod arbitrary;
18#[cfg(feature = "borsh")]
19mod borsh;
20#[cfg(all(feature = "quickcheck", any(feature = "std", feature = "alloc")))]
21mod quickcheck;
22#[cfg(feature = "serde")]
23mod serde;
24
25#[cfg(feature = "pyo3")]
26mod python;
27
28#[cfg(feature = "wasm")]
29mod wasm;
30
31#[doc = "UTF-8 validated wrapper around `Buffer` with a String-like interface."]
32#[doc = ""]
33#[doc = "Guarantees valid UTF-8. Split/slice operations check char boundaries."]
34#[doc = "Fixed inline capacity of 62 bytes."]
35#[cfg_attr(not(feature = "wasm"), doc = "")]
36#[cfg_attr(not(feature = "wasm"), doc = "# Examples")]
37#[cfg_attr(not(feature = "wasm"), doc = "")]
38#[cfg_attr(not(feature = "wasm"), doc = "```")]
39#[cfg_attr(not(feature = "wasm"), doc = "use smol_bytes::Utf8Buffer;")]
40#[cfg_attr(not(feature = "wasm"), doc = "")]
41#[cfg_attr(not(feature = "wasm"), doc = "let mut buf = Utf8Buffer::new();")]
42#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"Hello\");")]
43#[cfg_attr(not(feature = "wasm"), doc = "buf.push(' ');")]
44#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"world!\");")]
45#[cfg_attr(
46 not(feature = "wasm"),
47 doc = "assert_eq!(buf.as_str(), \"Hello world!\");"
48)]
49#[cfg_attr(not(feature = "wasm"), doc = "```")]
50#[cfg_attr(feature = "wasm", doc = "")]
51#[cfg_attr(feature = "wasm", doc = "@example")]
52#[cfg_attr(feature = "wasm", doc = "```typescript")]
53#[cfg_attr(feature = "wasm", doc = "import { Utf8Buffer } from 'smol-bytes';")]
54#[cfg_attr(feature = "wasm", doc = "const buf = new Utf8Buffer();")]
55#[cfg_attr(feature = "wasm", doc = "buf.pushStr('hello world');")]
56#[cfg_attr(
57 feature = "wasm",
58 doc = "console.log(buf.toString()); // 'hello world'"
59)]
60#[cfg_attr(feature = "wasm", doc = "```")]
61#[derive(Clone, Copy)]
62#[cfg_attr(feature = "pyo3", ::pyo3::prelude::pyclass)]
63#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
64pub struct Utf8Buffer {
65 inner: Buffer,
66}
67
68impl Default for Utf8Buffer {
69 #[cfg_attr(not(coverage), inline(always))]
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl sealed::Sealed for Utf8Buffer {}
76
77impl core::fmt::Write for Utf8Buffer {
78 fn write_str(&mut self, s: &str) -> core::fmt::Result {
79 self.try_push_str(s).map_err(|_| core::fmt::Error)
80 }
81}
82
83struct FormatWriteAdapter<'a> {
84 candidate: &'a mut Utf8Buffer,
85 capacity_error: Option<TryPutError>,
86}
87
88impl core::fmt::Write for FormatWriteAdapter<'_> {
89 fn write_str(&mut self, s: &str) -> core::fmt::Result {
90 match self.candidate.try_push_str(s) {
91 Ok(()) => Ok(()),
92 Err(error) => {
93 if self.capacity_error.is_none() {
94 self.capacity_error = Some(error);
95 }
96 Err(core::fmt::Error)
97 }
98 }
99 }
100}
101
102impl Utf8Buffer {
103 #[cfg_attr(not(coverage), inline(always))]
114 pub const fn new() -> Self {
115 Self {
116 inner: Buffer::new(),
117 }
118 }
119
120 #[cfg_attr(not(coverage), inline(always))]
139 pub const fn from_static(s: &'static str) -> Self {
140 let bytes = s.as_bytes();
141 assert!(
142 bytes.len() <= crate::INLINE_CAP,
143 "string too large for Utf8Buffer inline capacity"
144 );
145 Self {
147 inner: unsafe { Buffer::copy_from_slice(bytes) },
148 }
149 }
150
151 #[cfg_attr(not(coverage), inline(always))]
153 pub const fn as_inner(&self) -> &Buffer {
154 &self.inner
155 }
156
157 #[cfg_attr(not(coverage), inline(always))]
159 pub const fn into_inner(self) -> Buffer {
160 self.inner
161 }
162
163 #[cfg_attr(not(coverage), inline(always))]
174 pub fn as_str(&self) -> &str {
175 unsafe { str::from_utf8_unchecked(self.inner.as_slice()) }
177 }
178
179 pub fn as_mut_str(&mut self) -> &mut str {
184 str::from_utf8_mut(self.inner.as_mut_slice()).expect("UTF-8 invariant")
185 }
186
187 #[cfg_attr(not(coverage), inline(always))]
198 pub const fn len(&self) -> usize {
199 self.inner.len()
200 }
201
202 #[cfg_attr(not(coverage), inline(always))]
213 pub const fn is_empty(&self) -> bool {
214 self.inner.is_empty()
215 }
216
217 #[cfg_attr(not(coverage), inline(always))]
230 pub const fn remaining(&self) -> usize {
231 self.inner.remaining()
232 }
233
234 #[cfg_attr(not(coverage), inline(always))]
236 pub const fn capacity(&self) -> usize {
237 self.inner.capacity()
238 }
239
240 pub fn try_reclaim(&mut self, additional: usize) -> bool {
254 self.inner.try_reclaim(additional)
255 }
256
257 pub fn insert_str(&mut self, idx: usize, s: &str) {
273 self
274 .try_insert_str(idx, s)
275 .unwrap_or_else(|error| panic!("insert_str failed: {error}"));
276 }
277
278 pub fn try_insert_str(&mut self, idx: usize, s: &str) -> Result<(), Utf8EditError> {
283 validate_char_boundary(self.as_str(), idx).map_err(Utf8EditError::Index)?;
284 self.replace_range_validated(idx, idx, s)
285 }
286
287 pub fn insert(&mut self, idx: usize, ch: char) {
293 self
294 .try_insert(idx, ch)
295 .unwrap_or_else(|error| panic!("insert failed: {error}"));
296 }
297
298 pub fn try_insert(&mut self, idx: usize, ch: char) -> Result<(), Utf8EditError> {
300 let mut encoded = [0_u8; 4];
301 self.try_insert_str(idx, ch.encode_utf8(&mut encoded))
302 }
303
304 pub fn replace_range(&mut self, range: impl RangeBounds<usize>, replacement: &str) {
310 self
311 .try_replace_range(range, replacement)
312 .unwrap_or_else(|error| panic!("replace_range failed: {error}"));
313 }
314
315 pub fn try_replace_range(
320 &mut self,
321 range: impl RangeBounds<usize>,
322 replacement: &str,
323 ) -> Result<(), Utf8EditError> {
324 let (start, end) =
325 range_bounds_to_start_end(self.as_str(), range).map_err(Utf8EditError::Index)?;
326 self.replace_range_validated(start, end, replacement)
327 }
328
329 pub fn extend_from_within(&mut self, range: impl RangeBounds<usize>) {
336 self
337 .try_extend_from_within(range)
338 .unwrap_or_else(|error| panic!("extend_from_within failed: {error}"));
339 }
340
341 pub fn try_extend_from_within(
346 &mut self,
347 range: impl RangeBounds<usize>,
348 ) -> Result<(), Utf8EditError> {
349 let old_len = self.len();
350 let (start, end) = range_bounds_to_start_end(self.as_str(), range)?;
351 let count = end - start;
352 let new_len = old_len.checked_add(count).expect("buffer length overflow");
353 let available = self.inner.remaining_mut();
354 if count > available {
355 return Err(
356 TryPutError {
357 requested: count,
358 available,
359 }
360 .into(),
361 );
362 }
363
364 if count == 0 {
365 return Ok(());
366 }
367
368 self.inner.resize(new_len, 0);
369 self.inner.as_mut_slice().copy_within(start..end, old_len);
370 Ok(())
371 }
372
373 fn replace_range_validated(
374 &mut self,
375 start: usize,
376 end: usize,
377 replacement: &str,
378 ) -> Result<(), Utf8EditError> {
379 let old_len = self.inner.len();
380 let removed = end - start;
381 let growth = replacement.len().saturating_sub(removed);
382 let available = self.inner.capacity() - old_len;
383 if growth > available {
384 return Err(
385 TryPutError {
386 requested: growth,
387 available,
388 }
389 .into(),
390 );
391 }
392
393 let mut storage = RawUtf8Storage::fixed(&mut self.inner);
394 raw_replace_bytes(&mut storage, start, end, replacement.as_bytes());
395 Ok(())
396 }
397
398 pub fn remove(&mut self, idx: usize) -> char {
405 self
406 .try_remove(idx)
407 .unwrap_or_else(|error| panic!("remove failed: {error}"))
408 }
409
410 pub fn try_remove(&mut self, idx: usize) -> Result<char, Utf8IndexError> {
412 let len = self.len();
413 if idx >= len {
414 return Err(OutOfBounds::new(idx, len).into());
415 }
416 validate_char_boundary(self.as_str(), idx)?;
417 let ch = self.as_str()[idx..]
418 .chars()
419 .next()
420 .expect("index below string length has a scalar");
421 self
422 .replace_range_validated(idx, idx + ch.len_utf8(), "")
423 .expect("removing a scalar cannot exceed fixed capacity");
424 Ok(ch)
425 }
426
427 pub fn retain<F>(&mut self, mut predicate: F)
441 where
442 F: FnMut(char) -> bool,
443 {
444 let mut guard = RetainGuard::new(RawUtf8Storage::fixed(&mut self.inner));
445 guard.retain(&mut predicate);
446 }
447
448 pub fn drain(&mut self, range: impl RangeBounds<usize>) -> Utf8Drain<'_> {
454 self.try_drain(range).expect("drain range failed")
455 }
456
457 pub fn try_drain(
462 &mut self,
463 range: impl RangeBounds<usize>,
464 ) -> Result<Utf8Drain<'_>, Utf8IndexError> {
465 let (start, end) = range_bounds_to_start_end(self.as_str(), range)?;
466 Ok(Utf8Drain::new(
467 RawUtf8Storage::fixed(&mut self.inner),
468 start,
469 end,
470 ))
471 }
472
473 pub fn push(&mut self, ch: char) {
496 <Self as Utf8BufMut>::push(self, ch)
497 }
498
499 pub fn push_str(&mut self, s: &str) {
521 <Self as Utf8BufMut>::push_str(self, s)
522 }
523
524 pub fn try_push(&mut self, ch: char) -> Result<(), TryPutError> {
542 let mut buf = [0u8; 4];
543 let s = ch.encode_utf8(&mut buf);
544 self.inner.try_put_slice(s.as_bytes())
545 }
546
547 pub fn try_push_str(&mut self, s: &str) -> Result<(), TryPutError> {
564 self.inner.try_put_slice(s.as_bytes())
565 }
566
567 pub fn try_write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> Result<(), Utf8FormatError> {
573 let mut candidate = *self;
574 let (format_result, capacity_error) = {
575 let mut writer = FormatWriteAdapter {
576 candidate: &mut candidate,
577 capacity_error: None,
578 };
579 let result = core::fmt::write(&mut writer, args);
580 (result, writer.capacity_error)
581 };
582
583 if let Some(error) = capacity_error {
584 return Err(error.into());
585 }
586 if let Err(error) = format_result {
587 return Err(error.into());
588 }
589
590 *self = candidate;
591 Ok(())
592 }
593
594 pub fn clear(&mut self) {
596 self.inner.clear();
597 }
598
599 pub fn truncate(&mut self, new_len: usize) {
605 self
606 .try_truncate(new_len)
607 .expect("new_len must lie on a UTF-8 character boundary");
608 }
609
610 pub fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8IndexError> {
612 <Self as Utf8BufMut>::try_truncate(self, new_len)
613 }
614
615 pub fn pop(&mut self) -> Option<char> {
628 let ch = self.as_str().chars().next_back()?;
629 self.truncate(self.len() - ch.len_utf8());
630 Some(ch)
631 }
632
633 pub fn split_to(&mut self, at: usize) -> Self {
651 <Self as Utf8Buf>::split_to(self, at)
652 }
653
654 pub fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
661 <Self as Utf8Buf>::try_split_to(self, at)
662 }
663
664 pub fn split_off(&mut self, at: usize) -> Self {
682 <Self as Utf8Buf>::split_off(self, at)
683 }
684
685 pub fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
692 <Self as Utf8Buf>::try_split_off(self, at)
693 }
694
695 pub fn slice(&self, range: impl core::ops::RangeBounds<usize>) -> Self {
711 <Self as Utf8Buf>::slice(self, range)
712 }
713
714 pub fn try_slice(
721 &self,
722 range: impl core::ops::RangeBounds<usize>,
723 ) -> Result<Self, Utf8IndexError> {
724 <Self as Utf8Buf>::try_slice(self, range)
725 }
726}
727
728impl AsRef<str> for Utf8Buffer {
729 #[cfg_attr(not(coverage), inline(always))]
730 fn as_ref(&self) -> &str {
731 self.as_str()
732 }
733}
734
735impl AsRef<[u8]> for Utf8Buffer {
736 #[cfg_attr(not(coverage), inline(always))]
737 fn as_ref(&self) -> &[u8] {
738 self.as_str().as_bytes()
739 }
740}
741
742impl Borrow<str> for Utf8Buffer {
743 #[cfg_attr(not(coverage), inline(always))]
744 fn borrow(&self) -> &str {
745 self.as_str()
746 }
747}
748
749impl core::ops::Deref for Utf8Buffer {
750 type Target = str;
751
752 #[cfg_attr(not(coverage), inline(always))]
753 fn deref(&self) -> &Self::Target {
754 self.as_str()
755 }
756}
757
758#[cfg(feature = "std")]
759impl AsRef<std::ffi::OsStr> for Utf8Buffer {
760 fn as_ref(&self) -> &std::ffi::OsStr {
761 std::ffi::OsStr::new(self.as_str())
762 }
763}
764
765#[cfg(feature = "std")]
766impl AsRef<std::path::Path> for Utf8Buffer {
767 fn as_ref(&self) -> &std::path::Path {
768 std::path::Path::new(self.as_str())
769 }
770}
771
772impl Utf8Buf for Utf8Buffer {
773 fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
774 validate_char_boundary(self.as_str(), at)?;
775
776 Ok(Self {
777 inner: self.inner.try_split_to(at).expect("already checked bounds"),
778 })
779 }
780
781 fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
782 validate_char_boundary(self.as_str(), at)?;
783
784 Ok(Self {
785 inner: self
786 .inner
787 .try_split_off(at)
788 .expect("already checked bounds"),
789 })
790 }
791
792 fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, Utf8IndexError> {
793 let (start, end) = range_bounds_to_start_end(self.as_str(), range)?;
794
795 Ok(Self {
796 inner: self
797 .inner
798 .try_slice(start..end)
799 .expect("already checked bounds"),
800 })
801 }
802}
803
804impl Utf8BufMut for Utf8Buffer {
805 type CapacityError = TryPutError;
806
807 fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8IndexError> {
808 if new_len >= self.len() {
809 return Ok(());
810 }
811 if !self.as_str().is_char_boundary(new_len) {
812 return Err(Utf8IndexError::InvalidCharBoundary { index: new_len });
813 }
814 self.inner.truncate(new_len);
815 Ok(())
816 }
817
818 fn try_push(&mut self, ch: char) -> Result<(), Self::CapacityError> {
819 let mut buf = [0u8; 4];
820 let s = ch.encode_utf8(&mut buf);
821 self.inner.try_put_slice(s.as_bytes())
822 }
823
824 fn try_push_str(&mut self, s: &str) -> Result<(), Self::CapacityError> {
825 self.inner.try_put_slice(s.as_bytes())
826 }
827
828 fn clear(&mut self) {
829 self.inner.clear();
830 }
831}