bufjson/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::{cmp::Ordering, fmt, io::Cursor};
4
5pub mod lexical;
6pub mod syntax;
7
8#[cfg(doctest)]
9use doc_comment::doctest;
10#[cfg(doctest)]
11doctest!("../README.md");
12
13/// Position in an input buffer or stream.
14#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub struct Pos {
16 /// Zero-based byte offset from the start of the stream.
17 ///
18 /// The first byte in the stream has `offset` zero, the second `offset` one, and so on.
19 pub offset: usize,
20
21 /// One-based line offset from the start of the stream.
22 ///
23 /// The first byte in the stream is on `line` one, the first byte following the first line
24 /// breaking sequence is on line two, and so on. One-based indexing is used for `line` because
25 /// line numbers are primarily for consumption by humans, as opposed to byte offsets, which are
26 /// primarily for consumption by computers.
27 pub line: usize,
28
29 /// One-based column offset from the start of the line, where columns are measured in
30 /// characters. One-based indexing is used for `col` because column numbers are primarily for
31 /// consumption by humans, as opposed to byte offsets, which are primarily for consumption by
32 /// computers.
33 ///
34 /// The first byte in the stream is at `col` one, and whenever the line number is incremented,
35 /// the first byte on the next line is at `col` one. Each column number increment corresponds
36 /// to a full valid UTF-8 character.
37 ///
38 /// Note that the [JSON spec][rfc] only allows multi-byte UTF-8 within string values. Outside of
39 /// strings, every one byte always equals one column; but inside a string, a valid two-, three-,
40 /// or four-byte UTF-8 sequence will only increment the column count by 1.
41 ///
42 /// [rfc]: https://datatracker.ietf.org/doc/html/rfc8259
43 pub col: usize,
44}
45
46impl Pos {
47 /// Creates a new `Pos`.
48 #[inline(always)]
49 pub const fn new(offset: usize, line: usize, col: usize) -> Self {
50 Self { offset, line, col }
51 }
52
53 #[inline(always)]
54 pub(crate) fn advance_line(&mut self) {
55 self.offset += 1;
56 self.line += 1;
57 self.col = 1;
58 }
59
60 #[inline(always)]
61 pub(crate) fn advance_line_no_offset(&mut self) {
62 self.line += 1;
63 self.col = 1;
64 }
65
66 #[inline(always)]
67 pub(crate) fn advance_col(&mut self) {
68 self.offset += 1;
69 self.col += 1;
70 }
71
72 #[inline(always)]
73 pub(crate) fn advance_offset(&mut self, by: usize) {
74 self.offset += by;
75 }
76}
77
78impl Default for Pos {
79 fn default() -> Self {
80 Self {
81 offset: 0,
82 line: 1,
83 col: 1,
84 }
85 }
86}
87
88impl fmt::Display for Pos {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 write!(
91 f,
92 "line {}, column {} (offset: {})",
93 self.line, self.col, self.offset
94 )
95 }
96}
97
98/// Error returned when a [`Buf`] does not have enough bytes remaining to satisfy a request.
99#[derive(Copy, Clone, Debug, Eq, PartialEq)]
100pub struct BufUnderflow {
101 // Number of bytes requested from the buffer.
102 pub requested: usize,
103
104 // Number of bytes available in the buffer.
105 pub remaining: usize,
106}
107
108impl fmt::Display for BufUnderflow {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(
111 f,
112 "not enough bytes in buffer ({} requested, but only {} remain)",
113 self.requested, self.remaining,
114 )
115 }
116}
117
118impl std::error::Error for BufUnderflow {}
119
120/// Valid UTF-8 sequence whose bytes may or may not be contiguous in memory.
121///
122/// A `Buf` is a cursor into an in-memory buffer whose internal representation may be contiguous or
123/// split across multiple pieces stored at different memory locations. It can be thought of as an
124/// efficient iterator over the bytes of a UTF-8 string. Reading from a `Buf` advances the cursor
125/// position.
126///
127/// The simplest `Buf` is a `&[u8]`.
128///
129/// # Invariant
130///
131/// A new `Buf` value must only contain valid UTF-8 byte sequences.
132///
133/// Since a `Buf` may not be contiguous in memory, and bytes may be consumed in arbitrary
134/// quantities, individual method calls like [`chunk`] or [`copy_to_slice`] might return byte
135/// sequences with incomplete UTF-8 characters at the boundaries. However, consuming all bytes from
136/// a new `Buf` from start to finish will always yield valid UTF-8.
137///
138/// # Attribution
139///
140/// The design for this trait, including many method names, examples, and documentation passages, is
141/// borrowed from the trait of the same name in the `bytes` crate, which
142/// [is licensed][bytes-license] under the MIT License.
143///
144/// Note however that unlike `bytes::Buf`, which can contain arbitrary bytes, this trait **only**
145/// ever contains valid UTF-8 byte sequences.
146///
147/// # Example
148///
149/// ```
150/// use bufjson::Buf;
151///
152/// let mut buf = "hello, world".as_bytes();
153/// let mut dst = [0; 5];
154///
155/// buf.copy_to_slice(&mut dst);
156/// assert_eq!(b"hello", &dst);
157/// assert_eq!(7, buf.remaining());
158///
159/// buf.advance(2);
160/// buf.copy_to_slice(&mut dst);
161/// assert_eq!(b"world", &dst);
162/// assert!(!buf.has_remaining());
163/// ```
164///
165/// [`chunk`]: method@Self::chunk
166/// [`copy_to_slice`]: method@Self::copy_to_slice
167/// [bytes-license]: https://github.com/tokio-rs/bytes/blob/master/LICENSE
168pub trait Buf {
169 /// Advances the internal cursor.
170 ///
171 /// The next call to [`chunk`] will return a slice starting `n` bytes further into the buffer.
172 ///
173 /// # Panics
174 ///
175 /// Panics if `n > self.remaining()`.
176 ///
177 /// # Example
178 ///
179 /// ```
180 /// use bufjson::{Buf, IntoBuf};
181 ///
182 /// let mut buf = "hello, world".into_buf();
183 /// assert_eq!(b"hello, world", buf);
184 ///
185 /// buf.advance(7);
186 /// assert_eq!(b"world", buf);
187 /// ```
188 ///
189 /// [`chunk`]: method@Self::chunk
190 fn advance(&mut self, n: usize);
191
192 /// Returns a slice of bytes starting at the current position, with length between 0 and
193 /// [`remaining`].
194 ///
195 /// The returned slice may be shorter than [`remaining`] to accommodate non-contiguous internal
196 /// representations. An empty slice is returned only when [`remaining`] returns 0, and is always
197 /// returned in this case since this method never panics.
198 ///
199 /// Calling `chunk` does not advance the internal cursor.
200 ///
201 /// # Example
202 ///
203 /// ```
204 /// use bufjson::Buf;
205 ///
206 /// let mut buf = "hello, world".as_bytes();
207 /// assert_eq!(b"hello, world", buf.chunk());
208 ///
209 /// buf.advance(7);
210 /// assert_eq!(b"world", buf.chunk()); // A `chunk` call does not advance the internal cursor...
211 /// assert_eq!(b"world", buf.chunk()); // ...so calling it again returns the same value.
212 /// ```
213 ///
214 /// ```
215 /// use bufjson::{Buf, IntoBuf};
216 ///
217 /// // An empty chunk is returned if, and only if, the `Buf` has no remaining bytes.
218 /// assert_eq!(0, "".into_buf().remaining());
219 /// assert!("".into_buf().chunk().is_empty());
220 /// ```
221 ///
222 /// [`remaining`]: method@Self::remaining
223 fn chunk(&self) -> &[u8];
224
225 /// Returns the number of bytes between the current position and the end of the buffer.
226 ///
227 /// This value is always greater than or equal to the length of the slice returned by [`chunk`].
228 ///
229 /// # Example
230 ///
231 /// ```
232 /// use bufjson::Buf;
233 ///
234 /// let mut buf = "hello, world".as_bytes();
235 /// assert_eq!(12, buf.remaining());
236 ///
237 /// buf.advance(7);
238 /// assert_eq!(5, buf.remaining());
239 /// ```
240 ///
241 /// [`chunk`]: method@Self::chunk
242 fn remaining(&self) -> usize;
243
244 /// Copies bytes from `self` into `dst`.
245 ///
246 /// Advances the internal cursor by the number of bytes copied.
247 ///
248 /// Returns a buffer underflow error without advancing the cursor if `self` does not have enough
249 /// bytes [`remaining`] to fill `dst`.
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// use bufjson::{Buf, IntoBuf};
255 ///
256 /// let mut buf = "hello, world".into_buf();
257 /// let mut dst = [0; 5];
258 ///
259 /// assert_eq!(Ok(()), buf.try_copy_to_slice(&mut dst));
260 /// assert_eq!(b"hello", &dst);
261 /// assert_eq!(7, buf.remaining());
262 /// ```
263 ///
264 /// ```
265 /// use bufjson::{Buf, BufUnderflow};
266 ///
267 /// let mut dst = [0; 13];
268 ///
269 /// assert_eq!(
270 /// Err(BufUnderflow { requested: 13, remaining: 12 }),
271 /// "hello, world".as_bytes().try_copy_to_slice(&mut dst)
272 /// );
273 /// ```
274 ///
275 /// [`remaining`]: method@Self::remaining
276 fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), BufUnderflow>;
277
278 /// Returns true if there are more bytes to consume.
279 ///
280 /// This is a convenience method equivalent to `self.remaining() > 0`.
281 ///
282 /// # Example
283 ///
284 /// ```
285 /// use bufjson::Buf;
286 ///
287 /// let mut buf = "hello, world".as_bytes();
288 /// assert!(buf.has_remaining());
289 ///
290 /// buf.advance(12);
291 /// assert!(!buf.has_remaining());
292 /// ```
293 #[inline]
294 fn has_remaining(&self) -> bool {
295 self.remaining() != 0
296 }
297
298 /// Copies bytes from `self` into `dst`.
299 ///
300 /// Advances the internal cursor by the number of bytes copied.
301 ///
302 /// # Panics
303 ///
304 /// Panics if `self` does not have enough bytes [`remaining`] to fill `dst`.
305 ///
306 /// # Examples
307 ///
308 /// ```
309 /// use bufjson::Buf;
310 ///
311 /// let mut buf = "hello, world".as_bytes();
312 /// let mut dst = [0; 5];
313 ///
314 /// buf.copy_to_slice(&mut dst);
315 /// assert_eq!(b"hello", &dst);
316 /// assert_eq!(7, buf.remaining());
317 /// ```
318 ///
319 /// [`remaining`]: method@Self::remaining
320 #[inline]
321 fn copy_to_slice(&mut self, dst: &mut [u8]) {
322 if let Err(err) = self.try_copy_to_slice(dst) {
323 panic!("{err}");
324 }
325 }
326}
327
328/// Conversion into a [`Buf`].
329///
330/// By implementing `IntoBuf` for a type, you define how it will be converted into a `Buf`. This
331/// conversion is useful for types that represent valid UTF-8 byte sequences, whether or not all the
332/// bytes are contiguous in memory.
333///
334/// All implementations must respect the `Buf` invariant, namely that the new `Buf` produced by a
335/// call to [`into_buf`] must yield a valid UTF-8 byte sequence if read from beginning to end.
336///
337/// # Examples
338///
339/// Basic usage:
340///
341/// ```
342/// use bufjson::{Buf, IntoBuf};
343///
344/// let s = "hello, world";
345/// let mut buf = s.into_buf();
346///
347/// assert_eq!(12, buf.remaining());
348/// assert_eq!(b"hello, world", buf.chunk());
349///
350/// buf.advance(7);
351///
352/// let mut dst = [0; 5];
353/// buf.copy_to_slice(&mut dst);
354/// assert_eq!(b"world", &dst);
355/// ```
356///
357/// You can use `IntoBuf` as a trait bound. This allows the input type to change, so long as it can
358/// still be converted into a `Buf`. Additional bounds can be specified by restricting on `Buf`:
359///
360/// ```
361/// use bufjson::{Buf, IntoBuf};
362///
363/// fn collect_as_string<T>(input: T) -> String
364/// where
365/// T: IntoBuf,
366/// T::Buf: std::fmt::Debug,
367/// {
368/// let buf = input.into_buf();
369/// let mut v = Vec::with_capacity(buf.remaining());
370/// while buf.remaining() > 0 {
371/// v.copy_from_slice(buf.chunk());
372/// }
373///
374/// v.try_into()
375/// .expect("input must satisfy Buf invariant")
376/// }
377/// ```
378///
379/// [`into_buf`]: method@Self::into_buf
380pub trait IntoBuf {
381 // Type of `Buf` produced by this conversion.
382 type Buf: Buf;
383
384 /// Converts `self` into a `Buf`.
385 fn into_buf(self) -> Self::Buf;
386}
387
388impl Buf for &[u8] {
389 #[inline]
390 fn advance(&mut self, n: usize) {
391 if self.len() < n {
392 panic!(
393 "{}",
394 &BufUnderflow {
395 requested: n,
396 remaining: self.len()
397 }
398 );
399 } else {
400 *self = &self[n..];
401 }
402 }
403
404 #[inline(always)]
405 fn chunk(&self) -> &[u8] {
406 self
407 }
408
409 #[inline(always)]
410 fn remaining(&self) -> usize {
411 self.len()
412 }
413
414 #[inline]
415 fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), BufUnderflow> {
416 if self.len() < dst.len() {
417 Err(BufUnderflow {
418 requested: dst.len(),
419 remaining: self.len(),
420 })
421 } else {
422 dst.copy_from_slice(&self[..dst.len()]);
423 *self = &self[dst.len()..];
424 Ok(())
425 }
426 }
427}
428
429impl<'a> IntoBuf for &'a str {
430 type Buf = &'a [u8];
431
432 fn into_buf(self) -> Self::Buf {
433 self.as_bytes()
434 }
435}
436
437/// A [`Buf`] implementation for `String`.
438///
439/// # Example
440///
441/// ```
442/// use bufjson::{Buf, IntoBuf};
443///
444/// let mut buf = "hello, world".to_string().into_buf();
445/// let mut dst = [0; 5];
446///
447/// buf.copy_to_slice(&mut dst);
448/// assert_eq!(b"hello", &dst);
449/// assert_eq!(7, buf.remaining());
450/// ```
451#[derive(Debug)]
452pub struct StringBuf(Cursor<String>);
453
454impl Buf for StringBuf {
455 fn advance(&mut self, n: usize) {
456 let pos = self.0.position() as usize;
457 let len = self.0.get_ref().len();
458
459 if len < pos + n {
460 panic!(
461 "{}",
462 &BufUnderflow {
463 requested: n,
464 remaining: len - pos,
465 }
466 );
467 } else {
468 self.0.set_position((pos + n) as u64);
469 }
470 }
471
472 #[inline]
473 fn chunk(&self) -> &[u8] {
474 let pos = self.0.position() as usize;
475 let buf = self.0.get_ref().as_bytes();
476
477 &buf[pos..]
478 }
479
480 #[inline]
481 fn remaining(&self) -> usize {
482 let pos = self.0.position() as usize;
483 let len = self.0.get_ref().len();
484
485 len - pos
486 }
487
488 fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), BufUnderflow> {
489 let pos = self.0.position() as usize;
490 let len = self.0.get_ref().len();
491
492 if len < pos + dst.len() {
493 Err(BufUnderflow {
494 requested: dst.len(),
495 remaining: len - pos,
496 })
497 } else {
498 dst.copy_from_slice(&self.0.get_ref().as_bytes()[pos..pos + dst.len()]);
499 self.0.set_position((pos + dst.len()) as u64);
500
501 Ok(())
502 }
503 }
504}
505
506impl IntoBuf for String {
507 type Buf = StringBuf;
508
509 fn into_buf(self) -> Self::Buf {
510 StringBuf(Cursor::new(self))
511 }
512}
513
514/// Trait for types that form an [equivalent relation] together with `str`.
515///
516/// This trait without methods is equivalent in all respects to [`std::cmp::Eq`] excepting that it
517/// indicates that the type implementing it can be compared for equality with `str`.
518///
519/// [equivalence relation]: https://en.wikipedia.org/wiki/Equivalence_relation
520pub trait EqStr: for<'a> PartialEq<&'a str> {}
521
522impl EqStr for &'_ str {}
523
524/// Trait for types that form a [total ordering] together with `str`.
525///
526/// This trait may implemented by a type that is comparable to `str` such that the values of that
527/// type and `str` can be placed in a single total ordering. It is equivalent in all respects to
528/// [`std::cmp::Ord`] excepting that it indicates that the type implementing it joins together in a
529/// total ordering with `str`.
530///
531/// [total ordering]: https://en.wikipedia.org/wiki/Total_order
532pub trait OrdStr: EqStr + for<'a> PartialOrd<&'a str> {
533 /// Returns an [`Ordering`] between `self` and `other`.
534 fn cmp(&self, other: &str) -> Ordering;
535}
536
537impl OrdStr for &'_ str {
538 #[inline(always)]
539 fn cmp(&self, other: &str) -> Ordering {
540 (**self).cmp(other)
541 }
542}
543
544/// Comparison operation on any two [`Buf`] values or values that convert to [`Buf`].
545///
546/// `Buf` values are compared lexicographically by their byte values.
547///
548/// # Example
549///
550/// Rust's standard string comparison approach also does byte-by-byte lexicographical comparison.
551/// Consequently, two `&str` values will always have the same relative ordering as their `Buf`
552/// equivalent.
553///
554/// ```
555/// use bufjson::{IntoBuf, buf_cmp};
556/// use std::cmp::Ordering;
557///
558/// let a = "hello";
559/// let b = "world";
560///
561/// assert!(a < b);
562/// assert!(b > a);
563/// assert!(buf_cmp(a, b) == Ordering::Less);
564/// assert!(buf_cmp(b, a) == Ordering::Greater);
565/// ```
566pub fn buf_cmp<A: IntoBuf, B: IntoBuf>(a: A, b: B) -> Ordering {
567 let mut a = a.into_buf();
568 let (mut a_chunk, mut a_i) = (a.chunk(), 0);
569
570 let mut b = b.into_buf();
571 let (mut b_chunk, mut b_i) = (b.chunk(), 0);
572
573 loop {
574 if a_i == a_chunk.len() || b_i == b_chunk.len() {
575 if a_i == a_chunk.len() {
576 a.advance(a_chunk.len());
577 a_chunk = a.chunk();
578 a_i = 0;
579 }
580
581 if b_i == b_chunk.len() {
582 b.advance(b_chunk.len());
583 b_chunk = b.chunk();
584 b_i = 0;
585 }
586
587 if !a.has_remaining() && !b.has_remaining() {
588 return Ordering::Equal;
589 } else if !a.has_remaining() {
590 debug_assert!(a_chunk.is_empty());
591
592 return Ordering::Less;
593 } else if !b.has_remaining() {
594 debug_assert!(b_chunk.is_empty());
595
596 return Ordering::Greater;
597 }
598 }
599
600 debug_assert!(
601 a_i < a_chunk.len(),
602 "a_i ({a_i} >= a_chunk.len() ({})",
603 a_chunk.len()
604 );
605 debug_assert!(
606 b_i < b_chunk.len(),
607 "b_i ({b_i} >= b_chunk.len() ({})",
608 b_chunk.len()
609 );
610
611 let ord = a_chunk[a_i].cmp(&b_chunk[b_i]);
612 if ord != Ordering::Equal {
613 return ord;
614 }
615
616 a_i += 1;
617 b_i += 1;
618 }
619}
620
621#[allow(unused_macros)]
622#[cfg(debug_assertions)]
623macro_rules! stringify_known_utf8 {
624 ($name:ty, $v:expr) => {
625 <$name>::from_utf8($v).expect(concat!(
626 "SAFETY: input ",
627 stringify!(v),
628 " must only contain valid UTF-8 characters"
629 ))
630 };
631}
632
633#[allow(unused_macros)]
634#[cfg(not(debug_assertions))]
635macro_rules! stringify_known_utf8 {
636 ($name:ty, $v:expr) => {
637 unsafe { <$name>::from_utf8_unchecked($v) }
638 };
639}
640
641// Convert UTF-8 string content of a trusted `Buf` to a `String`.
642//
643// SAFETY: This function is only safe to call if the `Buf` passed in only contains valid UTF-8 byte
644// sequences.
645//
646// This is crate-internal, because it's not functionality we particularly need to export, as we
647// don't want to acquire responsibility for supporting every aspect of someone else's `Buf`
648// implementation.
649#[cfg(feature = "read")]
650pub(crate) fn buf_to_string<T: IntoBuf>(t: T) -> String {
651 let mut b = t.into_buf();
652 let mut v = Vec::with_capacity(b.remaining());
653
654 while b.has_remaining() {
655 let chunk = b.chunk();
656 v.extend(chunk);
657 b.advance(chunk.len());
658 }
659
660 stringify_known_utf8!(String, v)
661}
662
663// Print UTF-8 string content of a trusted `Buf` to a formatter.
664//
665// SAFETY: This function is only safe to call if the `Buf` passed in only contains valid UTF-8 byte
666// sequences.
667//
668// This is crate-internal, because it's not functionality we particularly need to export, as we
669// don't want to acquire responsibility for supporting every aspect of someone else's `Buf`
670// implementation.
671#[cfg(feature = "read")]
672pub(crate) fn buf_display<T: IntoBuf>(t: T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
673 let mut b = t.into_buf();
674
675 let n = b.remaining();
676 let chunk = b.chunk();
677 if chunk.len() >= n {
678 // Fast path: the entire buffer is in one chunk, so we can print it directly.
679 f.write_str(stringify_known_utf8!(str, chunk))
680 } else {
681 // Slow path: the buffer is split across multiple chunks, so we need to copy it into a temporary vector.
682 let mut v = Vec::with_capacity(n);
683
684 while b.has_remaining() {
685 let chunk = b.chunk();
686 v.extend(chunk);
687 b.advance(chunk.len());
688 }
689
690 f.write_str(stringify_known_utf8!(str, &v))
691 }
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697 use rstest::rstest;
698 use std::{fmt::Debug, ops::Deref};
699
700 #[test]
701 fn test_pos_new() {
702 assert_eq!(
703 Pos {
704 offset: 1,
705 line: 2,
706 col: 3
707 },
708 Pos::new(1, 2, 3)
709 );
710 }
711
712 #[test]
713 fn test_pos_default() {
714 assert_eq!(
715 Pos {
716 offset: 0,
717 line: 1,
718 col: 1
719 },
720 Pos::default()
721 );
722 }
723
724 #[test]
725 fn test_pos_display() {
726 assert_eq!(
727 "line 1, column 1 (offset: 0)",
728 format!("{}", Pos::default())
729 );
730 assert_eq!(
731 "line 77, column 8 (offset: 103)",
732 format!("{}", Pos::new(103, 77, 8))
733 );
734 }
735
736 #[rstest]
737 #[case("", 1)]
738 #[case(String::new(), 1)]
739 #[case("foo", 4)]
740 #[case("bar".to_string(), 10)]
741 #[should_panic(expected = "not enough bytes in buffer")]
742 fn test_buf_advance_panic<T: IntoBuf>(#[case] t: T, #[case] n: usize) {
743 let mut b = t.into_buf();
744
745 b.advance(n);
746 }
747
748 #[rstest]
749 #[case("foo", 0, "foo")]
750 #[case("foo", 1, "oo")]
751 #[case("foo", 2, "o")]
752 #[case("foo", 3, "")]
753 #[case("fo", 0, "fo")]
754 #[case("fo", 1, "o")]
755 #[case("fo", 2, "")]
756 #[case("f", 0, "f")]
757 #[case("f", 1, "")]
758 #[case("", 0, "")]
759 fn test_buf_advance_ok(#[case] s: &str, #[case] n: usize, #[case] expect: &str) {
760 fn exec_test<T: IntoBuf>(t: T, n: usize, expect: &str) {
761 let mut b = t.into_buf();
762
763 b.advance(n);
764
765 assert_eq!(expect, str::from_utf8(b.chunk()).unwrap());
766 assert_eq!(expect.len(), b.remaining());
767 }
768
769 exec_test(s, n, expect);
770 exec_test(s.to_string(), n, expect);
771 }
772
773 #[rstest]
774 #[case("")]
775 #[case("a")]
776 #[case("foo")]
777 fn test_buf_chunk(#[case] s: &str) {
778 fn exec_test<T: IntoBuf>(t: T, s: &str) {
779 let b = t.into_buf();
780
781 assert_eq!(s, str::from_utf8(b.chunk()).unwrap());
782 }
783
784 exec_test(s, s);
785 exec_test(s.to_string(), s);
786 }
787
788 #[rstest]
789 #[case("", 0, false)]
790 #[case("a", 1, true)]
791 #[case("foo", 3, true)]
792 fn test_buf_remaining(
793 #[case] s: &str,
794 #[case] expect_remaining: usize,
795 #[case] expect_has_remaining: bool,
796 ) {
797 fn exec_test<T: IntoBuf>(t: T, expect_remaining: usize, expect_has_remaining: bool) {
798 let b = t.into_buf();
799
800 assert_eq!(expect_remaining, b.remaining());
801 assert_eq!(expect_has_remaining, b.has_remaining());
802 }
803
804 exec_test(s, expect_remaining, expect_has_remaining);
805 exec_test(s.to_string(), expect_remaining, expect_has_remaining);
806 }
807
808 #[rstest]
809 #[case("", b"", "")]
810 #[case("a", b"", "a")]
811 #[case("a", b"a", "")]
812 #[case("bar", b"", "bar")]
813 #[case("bar", b"b", "ar")]
814 #[case("bar", b"ba", "r")]
815 #[case("bar", b"bar", "")]
816 fn test_buf_try_copy_to_slice_ok<const N: usize>(
817 #[case] s: &str,
818 #[case] expect: &[u8; N],
819 #[case] rem: &str,
820 ) {
821 fn exec_test<T: IntoBuf, const N: usize>(t: T, expect: &[u8; N], rem: &str) {
822 let mut b = t.into_buf();
823 let mut actual = [0; N];
824
825 let result = b.try_copy_to_slice(&mut actual);
826
827 assert_eq!(Ok(()), result);
828 assert_eq!(expect, &actual);
829 assert_eq!(rem.len(), b.remaining());
830 assert_eq!(rem, str::from_utf8(b.chunk()).unwrap());
831 }
832
833 exec_test(s, expect, rem);
834 exec_test(s.to_string(), expect, rem);
835 }
836
837 #[rstest]
838 #[case("", [0; 1])]
839 #[case("", [0; 2])]
840 #[case("a", [0; 2])]
841 #[case("foo", [0; 4])]
842 #[case("foo", [0; 99])]
843 fn test_buf_try_copy_to_slice_err<const N: usize>(#[case] s: &str, #[case] dst: [u8; N]) {
844 fn exec_test<T: IntoBuf + Clone + Debug + Deref<Target = str>, const N: usize>(
845 t: T,
846 mut dst: [u8; N],
847 ) {
848 let u = t.clone();
849 let mut b = t.into_buf();
850
851 let result = b.try_copy_to_slice(&mut dst);
852
853 assert_eq!(
854 Err(BufUnderflow {
855 remaining: u.len(),
856 requested: N
857 }),
858 result
859 );
860 assert_eq!(&*u, str::from_utf8(b.chunk()).unwrap());
861 }
862
863 exec_test(s, dst);
864 exec_test(s.to_string(), dst);
865 }
866
867 #[rstest]
868 #[case("", b"", "")]
869 #[case("a", b"", "a")]
870 #[case("a", b"a", "")]
871 #[case("bar", b"", "bar")]
872 #[case("bar", b"b", "ar")]
873 #[case("bar", b"ba", "r")]
874 #[case("bar", b"bar", "")]
875 fn test_buf_copy_to_slice_ok<const N: usize>(
876 #[case] s: &str,
877 #[case] expect: &[u8; N],
878 #[case] rem: &str,
879 ) {
880 fn exec_test<T: IntoBuf, const N: usize>(t: T, expect: &[u8; N], rem: &str) {
881 let mut b = t.into_buf();
882 let mut actual = [0; N];
883
884 b.copy_to_slice(&mut actual);
885
886 assert_eq!(expect, &actual);
887 assert_eq!(rem, str::from_utf8(b.chunk()).unwrap());
888 }
889
890 exec_test(s, expect, rem);
891 exec_test(s.to_string(), expect, rem);
892 }
893
894 #[test]
895 fn test_ord_str() {
896 assert_eq!(Ordering::Less, OrdStr::cmp(&"abc", "abd"));
897 assert_eq!(Ordering::Equal, OrdStr::cmp(&"abc", "abc"));
898 assert_eq!(Ordering::Greater, OrdStr::cmp(&"abd", "abc"));
899 }
900
901 #[test]
902 #[cfg(feature = "read")]
903 fn test_buf_to_string() {
904 assert_eq!("foo", buf_to_string("foo"));
905 }
906
907 #[test]
908 #[cfg(feature = "read")]
909 fn test_buf_display() {
910 struct Wrapper(&'static str);
911
912 impl fmt::Display for Wrapper {
913 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
914 buf_display(self.0.to_string(), f)
915 }
916 }
917
918 let wrapper = Wrapper("foo");
919
920 assert_eq!("foo", format!("{wrapper}"));
921 }
922}