Skip to main content

facet_toml/
error.rs

1//! Error types for TOML deserialization and serialization.
2
3use alloc::format;
4use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use core::fmt::{self, Display};
7
8use facet_reflect::{ReflectError, Span};
9
10/// Error type for TOML operations.
11#[derive(Debug, Clone)]
12pub struct TomlError {
13    /// The specific kind of error
14    pub kind: TomlErrorKind,
15    /// Source span where the error occurred
16    pub span: Option<Span>,
17    /// The source input (for diagnostics)
18    pub source_code: Option<String>,
19}
20
21impl Display for TomlError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        write!(f, "{}", self.kind)
24    }
25}
26
27impl std::error::Error for TomlError {}
28
29impl TomlError {
30    /// Create a new error with span information
31    pub const fn new(kind: TomlErrorKind, span: Span) -> Self {
32        TomlError {
33            kind,
34            span: Some(span),
35            source_code: None,
36        }
37    }
38
39    /// Create an error without span information
40    pub const fn without_span(kind: TomlErrorKind) -> Self {
41        TomlError {
42            kind,
43            span: None,
44            source_code: None,
45        }
46    }
47
48    /// Attach source code for rich diagnostics
49    pub fn with_source(mut self, source: &str) -> Self {
50        self.source_code = Some(source.to_string());
51        self
52    }
53}
54
55/// Specific error kinds for TOML operations
56#[derive(Debug, Clone)]
57pub enum TomlErrorKind {
58    /// Parse error from toml_parser
59    Parse(String),
60    /// Unexpected value type
61    UnexpectedType {
62        /// What type was expected
63        expected: &'static str,
64        /// What type was found
65        got: &'static str,
66    },
67    /// Unexpected end of input
68    UnexpectedEof {
69        /// What was expected before EOF
70        expected: &'static str,
71    },
72    /// Unknown field in table
73    UnknownField {
74        /// The unknown field name
75        field: String,
76        /// List of valid field names
77        expected: Vec<&'static str>,
78        /// Suggested field name (if similar to an expected field)
79        suggestion: Option<&'static str>,
80    },
81    /// Missing required field
82    MissingField {
83        /// The name of the missing field
84        field: &'static str,
85        /// Span of the table start (opening header)
86        table_start: Option<Span>,
87        /// Span of the table end
88        table_end: Option<Span>,
89    },
90    /// Invalid value for type
91    InvalidValue {
92        /// Description of why the value is invalid
93        message: String,
94    },
95    /// Reflection error from facet-reflect
96    Reflect(ReflectError),
97    /// Number out of range
98    NumberOutOfRange {
99        /// The numeric value that was out of range
100        value: String,
101        /// The target type that couldn't hold the value
102        target_type: &'static str,
103    },
104    /// Duplicate key in table
105    DuplicateKey {
106        /// The key that appeared more than once
107        key: String,
108    },
109    /// Invalid UTF-8 in string
110    InvalidUtf8(core::str::Utf8Error),
111    /// Solver error (for flattened types)
112    Solver(String),
113    /// Serialization error
114    Serialize(String),
115}
116
117impl Display for TomlErrorKind {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            TomlErrorKind::Parse(msg) => write!(f, "parse error: {msg}"),
121            TomlErrorKind::UnexpectedType { expected, got } => {
122                write!(f, "type mismatch: expected {expected}, got {got}")
123            }
124            TomlErrorKind::UnexpectedEof { expected } => {
125                write!(f, "unexpected end of input, expected {expected}")
126            }
127            TomlErrorKind::UnknownField {
128                field,
129                expected,
130                suggestion,
131            } => {
132                write!(f, "unknown field `{field}`, expected one of: {expected:?}")?;
133                if let Some(suggested) = suggestion {
134                    write!(f, " (did you mean `{suggested}`?)")?;
135                }
136                Ok(())
137            }
138            TomlErrorKind::MissingField { field, .. } => {
139                write!(f, "missing required field `{field}`")
140            }
141            TomlErrorKind::InvalidValue { message } => {
142                write!(f, "invalid value: {message}")
143            }
144            TomlErrorKind::Reflect(e) => write!(f, "reflection error: {e}"),
145            TomlErrorKind::NumberOutOfRange { value, target_type } => {
146                write!(f, "number `{value}` out of range for {target_type}")
147            }
148            TomlErrorKind::DuplicateKey { key } => {
149                write!(f, "duplicate key `{key}`")
150            }
151            TomlErrorKind::InvalidUtf8(e) => write!(f, "invalid UTF-8 sequence: {e}"),
152            TomlErrorKind::Solver(msg) => write!(f, "solver error: {msg}"),
153            TomlErrorKind::Serialize(msg) => write!(f, "serialization error: {msg}"),
154        }
155    }
156}
157
158impl TomlErrorKind {
159    /// Get an error code for this kind of error.
160    pub const fn code(&self) -> &'static str {
161        match self {
162            TomlErrorKind::Parse(_) => "toml::parse",
163            TomlErrorKind::UnexpectedType { .. } => "toml::type_mismatch",
164            TomlErrorKind::UnexpectedEof { .. } => "toml::unexpected_eof",
165            TomlErrorKind::UnknownField { .. } => "toml::unknown_field",
166            TomlErrorKind::MissingField { .. } => "toml::missing_field",
167            TomlErrorKind::InvalidValue { .. } => "toml::invalid_value",
168            TomlErrorKind::Reflect(_) => "toml::reflect",
169            TomlErrorKind::NumberOutOfRange { .. } => "toml::number_out_of_range",
170            TomlErrorKind::DuplicateKey { .. } => "toml::duplicate_key",
171            TomlErrorKind::InvalidUtf8(_) => "toml::invalid_utf8",
172            TomlErrorKind::Solver(_) => "toml::solver",
173            TomlErrorKind::Serialize(_) => "toml::serialize",
174        }
175    }
176
177    /// Get a label describing where/what the error points to.
178    pub fn label(&self) -> String {
179        match self {
180            TomlErrorKind::Parse(msg) => format!("parse error: {msg}"),
181            TomlErrorKind::UnexpectedType { expected, got } => {
182                format!("expected {expected}, got {got}")
183            }
184            TomlErrorKind::UnexpectedEof { expected } => format!("expected {expected}"),
185            TomlErrorKind::UnknownField {
186                field, suggestion, ..
187            } => {
188                if let Some(suggested) = suggestion {
189                    format!("unknown field '{field}' - did you mean '{suggested}'?")
190                } else {
191                    format!("unknown field '{field}'")
192                }
193            }
194            TomlErrorKind::MissingField { field, .. } => format!("missing field '{field}'"),
195            TomlErrorKind::InvalidValue { .. } => "invalid value".into(),
196            TomlErrorKind::Reflect(_) => "reflection error".into(),
197            TomlErrorKind::NumberOutOfRange { target_type, .. } => {
198                format!("out of range for {target_type}")
199            }
200            TomlErrorKind::DuplicateKey { key } => format!("duplicate key '{key}'"),
201            TomlErrorKind::InvalidUtf8(_) => "invalid UTF-8".into(),
202            TomlErrorKind::Solver(_) => "solver error".into(),
203            TomlErrorKind::Serialize(_) => "serialization error".into(),
204        }
205    }
206}
207
208impl From<ReflectError> for TomlError {
209    fn from(err: ReflectError) -> Self {
210        TomlError {
211            kind: TomlErrorKind::Reflect(err),
212            span: None,
213            source_code: None,
214        }
215    }
216}
217
218/// Result type for TOML operations
219#[allow(dead_code)]
220pub type Result<T> = core::result::Result<T, TomlError>;