toon_macro/
error.rs

1//! Unified error types for toon-macro.
2//!
3//! This module provides a single [`enum@Error`] type that wraps all possible
4//! errors from TOON parsing, serialization, and table operations.
5
6use thiserror::Error;
7
8/// A unified error type for all toon-macro operations.
9#[derive(Debug, Error)]
10pub enum Error {
11    /// Error during TOON serialization.
12    #[error("TOON serialization error: {0}")]
13    Serialize(String),
14
15    /// Error during TOON deserialization/parsing.
16    #[error("TOON deserialization error: {0}")]
17    Deserialize(String),
18
19    /// Invalid TOON table structure.
20    #[error("Invalid TOON table: {0}")]
21    InvalidTable(String),
22
23    /// A required column is missing from the table.
24    #[error("Missing required column: {0}")]
25    MissingColumn(&'static str),
26
27    /// Error converting between types.
28    #[error("Conversion error: {0}")]
29    ConversionError(String),
30
31    /// Invalid value type encountered.
32    #[error("Invalid value type: expected {expected}, got {got}")]
33    InvalidType {
34        /// The expected type name.
35        expected: &'static str,
36        /// The actual type name.
37        got: String,
38    },
39
40    /// Row index out of bounds.
41    #[error("Row index {index} out of bounds (table has {len} rows)")]
42    RowOutOfBounds {
43        /// The requested index.
44        index: usize,
45        /// The number of rows in the table.
46        len: usize,
47    },
48
49    /// Column index out of bounds.
50    #[error("Column index {index} out of bounds (table has {len} columns)")]
51    ColumnOutOfBounds {
52        /// The requested index.
53        index: usize,
54        /// The number of columns in the table.
55        len: usize,
56    },
57}
58
59/// A `Result` type alias using [`enum@Error`].
60pub type Result<T> = std::result::Result<T, Error>;
61
62impl From<serde_toon2::Error> for Error {
63    fn from(err: serde_toon2::Error) -> Self {
64        // Determine if it's a serialization or deserialization error
65        // based on the error message content
66        let msg = err.to_string();
67        if msg.contains("serialize") || msg.contains("Serialize") {
68            Error::Serialize(msg)
69        } else {
70            Error::Deserialize(msg)
71        }
72    }
73}
74
75impl Error {
76    /// Create a serialization error from a message.
77    pub fn serialize<S: Into<String>>(msg: S) -> Self {
78        Error::Serialize(msg.into())
79    }
80
81    /// Create a deserialization error from a message.
82    pub fn deserialize<S: Into<String>>(msg: S) -> Self {
83        Error::Deserialize(msg.into())
84    }
85
86    /// Create an invalid table error.
87    pub fn invalid_table<S: Into<String>>(msg: S) -> Self {
88        Error::InvalidTable(msg.into())
89    }
90
91    /// Create a missing column error.
92    pub fn missing_column(name: &'static str) -> Self {
93        Error::MissingColumn(name)
94    }
95
96    /// Create a conversion error.
97    pub fn conversion<S: Into<String>>(msg: S) -> Self {
98        Error::ConversionError(msg.into())
99    }
100
101    /// Create an invalid type error.
102    pub fn invalid_type(expected: &'static str, got: impl std::fmt::Debug) -> Self {
103        Error::InvalidType {
104            expected,
105            got: format!("{:?}", got),
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_error_display() {
116        let err = Error::MissingColumn("id");
117        assert_eq!(err.to_string(), "Missing required column: id");
118
119        let err = Error::InvalidType {
120            expected: "string",
121            got: "number".to_string(),
122        };
123        assert!(err.to_string().contains("expected string"));
124    }
125
126    #[test]
127    fn test_error_constructors() {
128        let err = Error::serialize("failed to write");
129        assert!(matches!(err, Error::Serialize(_)));
130
131        let err = Error::invalid_table("missing header");
132        assert!(matches!(err, Error::InvalidTable(_)));
133    }
134}