Skip to main content

wyrd/authoring/
serde_ron.rs

1//! RON load/save for weaves with validate-on-decode (`serde-ron` feature).
2//!
3//! The wire schema is [`WeaveDef`], matching the JSON codec. Parsing never
4//! produces an executable runtime — [`from_ron`] always runs full validation
5//! before returning an immutable [`Weave`].
6
7use core::fmt;
8
9use std::string::String;
10
11use crate::{ValidationError, Weave, WeaveDef};
12
13/// RON parse, serialize, or post-parse validation failure.
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum RonCodecError {
17    /// RON text could not be parsed into a [`WeaveDef`].
18    Parse {
19        /// Underlying `ron` parse error.
20        source: ron::error::SpannedError,
21        /// One-based line number for diagnostics.
22        line: usize,
23        /// One-based column number for diagnostics.
24        column: usize,
25    },
26    /// Parsed definition failed [`Weave`] validation.
27    Validation(ValidationError),
28    /// [`WeaveDef`] could not be serialized to RON.
29    Serialize(ron::Error),
30}
31
32impl fmt::Display for RonCodecError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Self::Parse {
36                source,
37                line,
38                column,
39            } => write!(
40                f,
41                "RON parse error at line {line}, column {column}: {source}"
42            ),
43            Self::Validation(error) => write!(f, "invalid RON weave: {error}"),
44            Self::Serialize(error) => write!(f, "RON serialization error: {error}"),
45        }
46    }
47}
48
49impl std::error::Error for RonCodecError {
50    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
51        match self {
52            Self::Parse { source, .. } => Some(source),
53            Self::Validation(source) => Some(source),
54            Self::Serialize(source) => Some(source),
55        }
56    }
57}
58
59impl From<ValidationError> for RonCodecError {
60    fn from(value: ValidationError) -> Self {
61        Self::Validation(value)
62    }
63}
64
65/// Parse RON text as a [`WeaveDef`] and validate into a [`Weave`].
66pub fn from_ron(text: &str) -> Result<Weave, RonCodecError> {
67    let def: WeaveDef = ron::from_str(text).map_err(|source: ron::error::SpannedError| {
68        let line = source.span.start.line;
69        let column = source.span.start.col;
70        RonCodecError::Parse {
71            source,
72            line,
73            column,
74        }
75    })?;
76    Ok(Weave::try_from(def)?)
77}
78
79/// Pretty-print a weave definition as RON.
80pub fn to_ron(weave: &Weave) -> Result<String, RonCodecError> {
81    ron::ser::to_string_pretty(&weave.to_def(), ron::ser::PrettyConfig::default())
82        .map_err(RonCodecError::Serialize)
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use std::error::Error as _;
89    use std::string::ToString;
90
91    struct FailingWriter;
92
93    impl fmt::Write for FailingWriter {
94        fn write_str(&mut self, _: &str) -> fmt::Result {
95            Err(fmt::Error)
96        }
97    }
98
99    #[test]
100    fn serialize_error_formats_and_retains_its_source() {
101        let source =
102            ron::ser::to_writer_pretty(FailingWriter, &"codec", ron::ser::PrettyConfig::default())
103                .expect_err("writer failure must be preserved as a RON error");
104        let error = RonCodecError::Serialize(source);
105
106        assert!(error.to_string().starts_with("RON serialization error: "));
107        assert!(error.source().is_some());
108    }
109}