1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::{error::Error, fmt};

/// Errors related to the decoding process.
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum DecodingError {
    /// Invalid header.
    InvalidHeader(&'static str),
    /// Missing tag bit at the given position.
    MissingTag(usize),
    /// Invalid cell index.
    InvalidCellIndex {
        /// Error message.
        reason: &'static str,
        /// Underlying validation error, if any.
        source: Option<h3o::error::InvalidCellIndex>,
    },
    /// Not enough data to decompress.
    NotEnoughData,
}

impl DecodingError {
    pub(crate) const fn bad_header(reason: &'static str) -> Self {
        Self::InvalidHeader(reason)
    }

    pub(crate) const fn missing_tag(position: usize) -> Self {
        Self::MissingTag(position)
    }

    pub(crate) const fn bad_index(
        reason: &'static str,
        source: Option<h3o::error::InvalidCellIndex>,
    ) -> Self {
        Self::InvalidCellIndex { reason, source }
    }

    pub(crate) const fn not_enough_data() -> Self {
        Self::NotEnoughData
    }
}

impl fmt::Display for DecodingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Self::InvalidHeader(reason) => {
                write!(f, "header error: {reason}")
            }
            Self::MissingTag(position) => {
                write!(f, "missing tag bit at {position}")
            }
            Self::InvalidCellIndex { reason, .. } => {
                write!(f, "invalid cell index: {reason}")
            }
            Self::NotEnoughData => write!(f, "truncated input"),
        }
    }
}

impl Error for DecodingError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        if let Self::InvalidCellIndex {
            source: Some(ref source),
            ..
        } = *self
        {
            return Some(source);
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use h3o::CellIndex;

    // All error must have a non-empty display.
    #[test]
    fn display() {
        assert!(!DecodingError::bad_header("invalid header")
            .to_string()
            .is_empty());

        assert!(!DecodingError::missing_tag(42).to_string().is_empty());

        assert!(!DecodingError::bad_index("invalid cell index", None)
            .to_string()
            .is_empty());

        assert!(!DecodingError::not_enough_data().to_string().is_empty());
    }

    // Check that source if forwarded when relevant.
    #[test]
    fn source() {
        assert!(DecodingError::bad_header("invalid header")
            .source()
            .is_none());

        assert!(DecodingError::missing_tag(42).source().is_none());

        assert!(DecodingError::bad_index("invalid cell index", None)
            .source()
            .is_none());
        assert!(DecodingError::bad_index(
            "not a cell index",
            CellIndex::try_from(0).err()
        )
        .source()
        .is_some());

        assert!(DecodingError::not_enough_data().source().is_none());
    }
}