jsn 0.14.0

A library for querying streaming JSON tokens
Documentation
use crate::error::{Position, Reason};
use std::io;

// Wrapper around a `Read`able instance.
//
// Keeps track of the line/column
#[derive(Debug)]
pub struct Input<R> {
    inner: R,
    // Byte offset of the next byte.
    next: usize,
    // Index of the current line. Characters in the first line of input are at line 1
    line: usize,
    // Index of the current column. The first character in the input is at col 1. Column
    // corresponds to when a newline has just been read.
    col: usize,
}

impl<R: io::Seek> Input<R> {
    // If the inner Reader also implements `Seek`, we can "reset" the Seek instance
    pub fn reset(&mut self) -> Result<(), Reason> {
        self.inner
            .rewind()
            .map_err(|e| Reason::CouldNotRewindInput(e.kind()))?;

        self.next = 0;
        Ok(())
    }
}

impl<R: io::Read> Input<R> {
    pub fn new(s: R) -> Self {
        Self {
            inner: s,
            next: 0,
            line: 1,
            col: 0,
        }
    }

    pub fn position(&self) -> Position {
        Position {
            offset: self.next.saturating_sub(1),
            line: self.line,
            col: self.col,
        }
    }

    #[inline]
    pub fn read_n<const N: usize>(&mut self) -> Result<Option<[u8; N]>, Reason> {
        let mut buf = [0; N];
        match self.inner.read_exact(&mut buf) {
            Ok(_) => {
                buf.iter().for_each(|b| {
                    if b == &b'\n' {
                        self.col = 0;
                        self.line += 1;
                    } else {
                        self.col += 1;
                    }
                });
                self.next += N;
                Ok(Some(buf))
            }
            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(None),
            Err(e) => Err(e.kind().into()),
        }
    }

    #[inline]
    fn read_byte(&mut self) -> Result<Option<u8>, Reason> {
        match self.read_n::<1>() {
            Ok(Some(buf)) => Ok(Some(buf[0])),
            Ok(None) => Ok(None),
            Err(e) => Err(e),
        }

        // let mut buf = [0; 1];
        // match self.inner.read_exact(&mut buf) {
        //     Ok(_) => {
        //         match buf {
        //             [b'\n'] => {
        //                 self.col = 0;
        //                 self.line += 1;
        //             }
        //             _ => {
        //                 self.col += 1;
        //             }
        //         }
        //         self.next += 1; Ok(Some(buf[0]))
        //     }
        //     Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(None),
        //     Err(e) => Err(e.kind().into()),
        // }
    }

    #[inline]
    pub fn next(&mut self) -> Result<Option<u8>, Reason> {
        self.read_byte()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;

    #[test]
    fn keeps_track_of_byte_offset() {
        let mut input = Input::new("he\r\nw\na\nb\nc".as_bytes());

        assert!(matches!(input.next(), Ok(Some(b'h'))));
        assert!(matches!(input.next(), Ok(Some(b'e'))));
        assert_eq!(
            input.position(),
            Position {
                offset: 1,
                line: 1,
                col: 2
            }
        );
        assert!(matches!(input.next(), Ok(Some(b'\r'))));
        assert!(matches!(input.next(), Ok(Some(b'\n'))));
        assert!(matches!(input.next(), Ok(Some(b'w'))));
        assert_eq!(
            input.position(),
            Position {
                offset: 4,
                line: 2,
                col: 1,
            }
        );
        for _ in 0..100 {
            let _ = input.next();
        }

        assert_eq!(
            input.position(),
            Position {
                offset: 10,
                line: 5,
                col: 1,
            }
        );
    }

    #[test]
    fn seekable_input_can_be_reset() {
        let cursor = Cursor::new("some text");
        let mut input = Input::new(cursor);
        assert!(matches!(input.next(), Ok(Some(b's'))));
        assert!(matches!(input.next(), Ok(Some(b'o'))));
        assert!(matches!(input.next(), Ok(Some(b'm'))));
        assert!(matches!(input.next(), Ok(Some(b'e'))));

        input.reset().unwrap();
        assert!(matches!(input.next(), Ok(Some(b's'))));
        assert!(matches!(input.next(), Ok(Some(b'o'))));
        assert!(matches!(input.next(), Ok(Some(b'm'))));
        assert!(matches!(input.next(), Ok(Some(b'e'))));
    }
}