1use core::{borrow::Borrow, ops::RangeBounds, str};
2
3use super::{
4 buffer::Buffer,
5 error::*,
6 utf8_buf::{Utf8Buf, Utf8BufMut},
7};
8
9mod cmp;
10mod fmt;
11mod from;
12mod ops;
13
14#[cfg(feature = "arbitrary")]
15mod arbitrary;
16#[cfg(feature = "borsh")]
17mod borsh;
18#[cfg(all(feature = "quickcheck", any(feature = "std", feature = "alloc")))]
19mod quickcheck;
20#[cfg(feature = "serde")]
21mod serde;
22
23#[cfg(feature = "pyo3")]
24mod python;
25
26#[cfg(feature = "wasm")]
27mod wasm;
28
29#[doc = "UTF-8 validated wrapper around `Buffer` with a String-like interface."]
30#[doc = ""]
31#[doc = "Guarantees valid UTF-8. Split/slice operations check char boundaries."]
32#[doc = "Fixed inline capacity of 62 bytes."]
33#[cfg_attr(not(feature = "wasm"), doc = "")]
34#[cfg_attr(not(feature = "wasm"), doc = "# Examples")]
35#[cfg_attr(not(feature = "wasm"), doc = "")]
36#[cfg_attr(not(feature = "wasm"), doc = "```")]
37#[cfg_attr(not(feature = "wasm"), doc = "use smol_bytes::Utf8Buffer;")]
38#[cfg_attr(not(feature = "wasm"), doc = "")]
39#[cfg_attr(not(feature = "wasm"), doc = "let mut buf = Utf8Buffer::new();")]
40#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"Hello\");")]
41#[cfg_attr(not(feature = "wasm"), doc = "buf.push(' ');")]
42#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"world!\");")]
43#[cfg_attr(
44 not(feature = "wasm"),
45 doc = "assert_eq!(buf.as_str(), \"Hello world!\");"
46)]
47#[cfg_attr(not(feature = "wasm"), doc = "```")]
48#[cfg_attr(feature = "wasm", doc = "")]
49#[cfg_attr(feature = "wasm", doc = "@example")]
50#[cfg_attr(feature = "wasm", doc = "```typescript")]
51#[cfg_attr(feature = "wasm", doc = "import { Utf8Buffer } from 'smol-bytes';")]
52#[cfg_attr(feature = "wasm", doc = "const buf = new Utf8Buffer();")]
53#[cfg_attr(feature = "wasm", doc = "buf.pushStr('hello world');")]
54#[cfg_attr(
55 feature = "wasm",
56 doc = "console.log(buf.toString()); // 'hello world'"
57)]
58#[cfg_attr(feature = "wasm", doc = "```")]
59#[derive(Clone, Copy)]
60#[cfg_attr(feature = "pyo3", ::pyo3::prelude::pyclass(skip_from_py_object))]
61#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
62pub struct Utf8Buffer {
63 inner: Buffer,
64}
65
66impl Default for Utf8Buffer {
67 #[cfg_attr(not(coverage), inline(always))]
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl Utf8Buffer {
74 #[cfg_attr(not(coverage), inline(always))]
85 pub const fn new() -> Self {
86 Self {
87 inner: Buffer::new(),
88 }
89 }
90
91 #[cfg_attr(not(coverage), inline(always))]
110 pub const fn from_static(s: &'static str) -> Self {
111 let bytes = s.as_bytes();
112 assert!(
113 bytes.len() <= crate::INLINE_CAP,
114 "string too large for Utf8Buffer inline capacity"
115 );
116 Self {
118 inner: unsafe { Buffer::copy_from_slice(bytes) },
119 }
120 }
121
122 #[cfg_attr(not(coverage), inline(always))]
124 pub const fn as_inner(&self) -> &Buffer {
125 &self.inner
126 }
127
128 #[cfg_attr(not(coverage), inline(always))]
130 pub const fn into_inner(self) -> Buffer {
131 self.inner
132 }
133
134 #[cfg_attr(not(coverage), inline(always))]
145 pub fn as_str(&self) -> &str {
146 unsafe { str::from_utf8_unchecked(self.inner.as_slice()) }
148 }
149
150 #[cfg_attr(not(coverage), inline(always))]
161 pub const fn len(&self) -> usize {
162 self.inner.len()
163 }
164
165 #[cfg_attr(not(coverage), inline(always))]
176 pub const fn is_empty(&self) -> bool {
177 self.inner.is_empty()
178 }
179
180 #[cfg_attr(not(coverage), inline(always))]
193 pub const fn remaining(&self) -> usize {
194 self.inner.remaining()
195 }
196
197 #[cfg_attr(not(coverage), inline(always))]
199 pub const fn capacity(&self) -> usize {
200 self.inner.capacity()
201 }
202
203 pub fn push(&mut self, ch: char) {
226 let mut buf = [0u8; 4];
227 let s = ch.encode_utf8(&mut buf);
228 self.inner.put_slice(s.as_bytes());
229 }
230
231 pub fn push_str(&mut self, s: &str) {
253 self.inner.put_slice(s.as_bytes());
254 }
255
256 pub fn try_push(&mut self, ch: char) -> Result<(), TryPutError> {
274 let mut buf = [0u8; 4];
275 let s = ch.encode_utf8(&mut buf);
276 self.inner.try_put_slice(s.as_bytes())
277 }
278
279 pub fn try_push_str(&mut self, s: &str) -> Result<(), TryPutError> {
296 self.inner.try_put_slice(s.as_bytes())
297 }
298
299 pub fn clear(&mut self) {
301 self.inner.clear();
302 }
303
304 pub fn truncate(&mut self, new_len: usize) {
310 self
311 .try_truncate(new_len)
312 .expect("new_len must lie on a UTF-8 character boundary");
313 }
314
315 pub fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8Error> {
317 <Self as Utf8BufMut>::try_truncate(self, new_len)
318 }
319
320 pub fn split_to(&mut self, at: usize) -> Self {
338 <Self as Utf8Buf>::split_to(self, at)
339 }
340
341 pub fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8Error> {
348 <Self as Utf8Buf>::try_split_to(self, at)
349 }
350
351 pub fn split_off(&mut self, at: usize) -> Self {
369 <Self as Utf8Buf>::split_off(self, at)
370 }
371
372 pub fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8Error> {
379 <Self as Utf8Buf>::try_split_off(self, at)
380 }
381
382 pub fn slice(&self, range: impl core::ops::RangeBounds<usize>) -> Self {
398 <Self as Utf8Buf>::slice(self, range)
399 }
400
401 pub fn try_slice(&self, range: impl core::ops::RangeBounds<usize>) -> Result<Self, Utf8Error> {
408 <Self as Utf8Buf>::try_slice(self, range)
409 }
410}
411
412impl AsRef<str> for Utf8Buffer {
413 #[cfg_attr(not(coverage), inline(always))]
414 fn as_ref(&self) -> &str {
415 self.as_str()
416 }
417}
418
419impl AsRef<[u8]> for Utf8Buffer {
420 #[cfg_attr(not(coverage), inline(always))]
421 fn as_ref(&self) -> &[u8] {
422 self.as_str().as_bytes()
423 }
424}
425
426impl Borrow<str> for Utf8Buffer {
427 #[cfg_attr(not(coverage), inline(always))]
428 fn borrow(&self) -> &str {
429 self.as_str()
430 }
431}
432
433impl core::ops::Deref for Utf8Buffer {
434 type Target = str;
435
436 #[cfg_attr(not(coverage), inline(always))]
437 fn deref(&self) -> &Self::Target {
438 self.as_str()
439 }
440}
441
442impl Utf8Buf for Utf8Buffer {
443 fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8Error> {
444 self.validate_char_boundary(at)?;
445
446 Ok(Self {
447 inner: self.inner.try_split_to(at).expect("already checked bounds"),
448 })
449 }
450
451 fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8Error> {
452 self.validate_char_boundary(at)?;
453
454 Ok(Self {
455 inner: self
456 .inner
457 .try_split_off(at)
458 .expect("already checked bounds"),
459 })
460 }
461
462 fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, Utf8Error> {
463 let (start, end) = self.range_bounds_to_start_end(range)?;
464
465 Ok(Self {
466 inner: self
467 .inner
468 .try_slice(start..end)
469 .expect("already checked bounds"),
470 })
471 }
472}
473
474impl Utf8BufMut for Utf8Buffer {
475 fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8Error> {
476 if new_len >= self.len() {
477 return Ok(());
478 }
479 if !self.as_str().is_char_boundary(new_len) {
480 return Err(Utf8Error::InvalidCharBoundary { at: new_len });
481 }
482 self.inner.truncate(new_len);
483 Ok(())
484 }
485
486 fn push(&mut self, ch: char) {
487 let mut buf = [0u8; 4];
488 let s = ch.encode_utf8(&mut buf);
489 self.inner.put_slice(s.as_bytes());
490 }
491
492 fn push_str(&mut self, s: &str) {
493 self.inner.put_slice(s.as_bytes());
494 }
495
496 fn clear(&mut self) {
497 self.inner.clear();
498 }
499}