Skip to main content

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}