Skip to main content

cjseq/
error.rs

1use crate::geometry::GeometryType;
2use std::fmt;
3
4/// The crate's error type.
5///
6/// Implemented by hand: this crate does not hand-write serde impls, and it
7/// does not take a derive dependency for its error type either.
8#[derive(Debug)]
9pub enum CjseqError {
10    /// A value was not the JSON the type expected, and nothing more specific
11    /// could be said about why.
12    Json(serde_json::Error),
13    /// A geometry's `boundaries` were nested to a depth its `type` does not
14    /// allow. serde reports this as "invalid type: integer, expected a
15    /// sequence", which names neither the geometry nor the depth it wanted;
16    /// this variant names both.
17    GeometryDepth {
18        geometry_type: GeometryType,
19        /// The nesting depth the geometry type requires.
20        expected: usize,
21        /// The nesting depth the `boundaries` actually had.
22        found: usize,
23    },
24    /// An I/O operation failed, eg opening or reading a CityJSON(Seq) file.
25    Io(std::io::Error),
26    /// A document parsed as valid JSON, and every typed field on it parsed
27    /// fine, but it still fails a structural rule this crate enforces beyond
28    /// what serde alone can express -- eg its `type` is not `"CityJSON"`, or
29    /// its `version` is not one this crate supports.
30    Validation(String),
31}
32
33impl fmt::Display for CjseqError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            CjseqError::Json(e) => write!(f, "invalid JSON: {e}"),
37            CjseqError::GeometryDepth {
38                geometry_type,
39                expected,
40                found,
41            } => write!(
42                f,
43                "a {geometry_type:?} nests its boundaries {expected} levels deep, but these boundaries are nested {found} levels deep"
44            ),
45            CjseqError::Io(e) => write!(f, "I/O error: {e}"),
46            CjseqError::Validation(msg) => write!(f, "invalid CityJSON: {msg}"),
47        }
48    }
49}
50
51impl std::error::Error for CjseqError {
52    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
53        match self {
54            CjseqError::Json(e) => Some(e),
55            CjseqError::GeometryDepth { .. } => None,
56            CjseqError::Io(e) => Some(e),
57            CjseqError::Validation(_) => None,
58        }
59    }
60}
61
62impl From<serde_json::Error> for CjseqError {
63    fn from(e: serde_json::Error) -> Self {
64        CjseqError::Json(e)
65    }
66}
67
68impl From<std::io::Error> for CjseqError {
69    fn from(e: std::io::Error) -> Self {
70        CjseqError::Io(e)
71    }
72}
73
74/// The crate's result type.
75pub type Result<T> = std::result::Result<T, CjseqError>;
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn io_error_converts_via_from_and_keeps_its_message() {
83        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "nope");
84        let err: CjseqError = io_err.into();
85        assert!(matches!(err, CjseqError::Io(_)));
86        assert_eq!(err.to_string(), "I/O error: nope");
87    }
88
89    #[test]
90    fn validation_error_carries_its_message() {
91        let err = CjseqError::Validation("Input file not CityJSON.".to_string());
92        assert_eq!(
93            err.to_string(),
94            "invalid CityJSON: Input file not CityJSON."
95        );
96    }
97}