Skip to main content

wyrd/authoring/
serde_json_codec.rs

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