neotoma 0.1.1

A flexible, cached parser combinator framework for Rust.
Documentation
//! Error types and result handling for parsing operations.
//!
//! This module defines the core error types used throughout the parsing library.
//! The [`Error`] enum distinguishes between expected parsing failures (`NoMatch`)
//! that are part of normal backtracking, and unexpected I/O errors from the
//! underlying data source.
//!
//! The [`ParseResult`] type alias provides a convenient shorthand for
//! `Result<T, Error>` used by all parsing operations.

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("No match")]
    NoMatch,
    #[error("I/O Error")]
    IO(#[from] std::io::Error),
}

pub type ParseResult<T> = std::result::Result<T, Error>;

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::{Error as IoError, ErrorKind};

    #[test]
    fn test_error_no_match_display() {
        let error = Error::NoMatch;
        assert_eq!(error.to_string(), "No match");
    }

    #[test]
    fn test_error_io_display() {
        let io_error = IoError::new(ErrorKind::UnexpectedEof, "test error");
        let error = Error::IO(io_error);
        assert_eq!(error.to_string(), "I/O Error");
    }

    #[test]
    fn test_error_from_io_error() {
        let io_error = IoError::new(ErrorKind::PermissionDenied, "access denied");
        let error: Error = io_error.into();

        match error {
            Error::IO(inner) => {
                assert_eq!(inner.kind(), ErrorKind::PermissionDenied);
                assert_eq!(inner.to_string(), "access denied");
            }
            _ => panic!("Expected IO error"),
        }
    }

    #[test]
    fn test_error_debug_format() {
        let error = Error::NoMatch;
        let debug_str = format!("{error:?}");
        assert_eq!(debug_str, "NoMatch");

        let io_error = IoError::new(ErrorKind::NotFound, "file not found");
        let error = Error::IO(io_error);
        let debug_str = format!("{error:?}");
        assert!(debug_str.starts_with("IO("));
        assert!(debug_str.contains("NotFound"));
    }

    #[test]
    #[allow(clippy::unnecessary_literal_unwrap)]
    fn test_parse_result_ok() {
        let result: ParseResult<String> = Ok("success".to_string());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "success");
    }

    #[test]
    #[allow(clippy::unnecessary_literal_unwrap)]
    fn test_parse_result_err_no_match() {
        let result: ParseResult<String> = Err(Error::NoMatch);
        assert!(result.is_err());

        match result.unwrap_err() {
            Error::NoMatch => (),
            _ => panic!("Expected NoMatch error"),
        }
    }

    #[test]
    #[allow(clippy::unnecessary_literal_unwrap)]
    fn test_parse_result_err_io() {
        let io_error = IoError::new(ErrorKind::InvalidData, "corrupt data");
        let result: ParseResult<String> = Err(Error::IO(io_error));
        assert!(result.is_err());

        match result.unwrap_err() {
            Error::IO(inner) => {
                assert_eq!(inner.kind(), ErrorKind::InvalidData);
                assert_eq!(inner.to_string(), "corrupt data");
            }
            _ => panic!("Expected IO error"),
        }
    }

    #[test]
    #[allow(clippy::unnecessary_literal_unwrap)]
    fn test_error_chain() {
        let io_error = IoError::new(ErrorKind::TimedOut, "operation timed out");
        let error = Error::from(io_error);

        let result: ParseResult<()> = Err(error);
        assert!(result.is_err());

        let error = result.unwrap_err();
        if let Error::IO(inner) = error {
            assert_eq!(inner.kind(), ErrorKind::TimedOut);
        } else {
            panic!("Expected IO error variant");
        }
    }

    #[test]
    fn test_different_io_error_kinds() {
        let error_kinds = vec![
            ErrorKind::NotFound,
            ErrorKind::PermissionDenied,
            ErrorKind::ConnectionRefused,
            ErrorKind::InvalidInput,
            ErrorKind::InvalidData,
            ErrorKind::TimedOut,
            ErrorKind::Interrupted,
            ErrorKind::UnexpectedEof,
        ];

        for kind in error_kinds {
            let io_error = IoError::new(kind, format!("test error: {kind:?}"));
            let error = Error::from(io_error);

            match error {
                Error::IO(inner) => assert_eq!(inner.kind(), kind),
                _ => panic!("Expected IO error for kind {kind:?}"),
            }
        }
    }

    #[test]
    fn test_error_equality() {
        // Test that NoMatch errors are equal
        assert!(matches!(Error::NoMatch, Error::NoMatch));

        // Note: IO errors don't implement PartialEq, so we can't test equality
        // But we can test that they're both IO variants
        let io1 = Error::IO(IoError::new(ErrorKind::NotFound, "test1"));
        let io2 = Error::IO(IoError::new(ErrorKind::NotFound, "test2"));

        match (&io1, &io2) {
            (Error::IO(_), Error::IO(_)) => (), // Both are IO errors
            _ => panic!("Both should be IO errors"),
        }
    }

    #[test]
    fn test_error_send_sync() {
        fn assert_send<T: Send>(_: T) {}
        fn assert_sync<T: Sync>(_: T) {}

        let error = Error::NoMatch;
        assert_send(error);

        let error = Error::NoMatch;
        assert_sync(error);

        let io_error = IoError::other("test");
        let error = Error::IO(io_error);
        assert_send(error);

        let io_error = IoError::other("test");
        let error = Error::IO(io_error);
        assert_sync(error);
    }
}