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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! This module defines error and result types.
//!

use crate::parser;
use std::result::Result;
use thiserror::Error;

/// A basic error type that contains a string.
#[allow(missing_docs)]
#[non_exhaustive]
#[derive(Debug, PartialEq, Error)]
pub enum ValidateError {
    /// An error during CDDL parsing.
    #[error(transparent)]
    ParseError(#[from] parser::ParseError),
    /// A logical error in the CDDL structure.
    #[error("Structural({0})")]
    Structural(String),
    /// A data mismatch during validation.
    // The difference between Mismatch and MapCut is that they trigger
    // slightly different internal behaivor; to a human reader they mean
    // the same thing so we will Display them the same way.
    #[error("Mismatch(expected {})", .0.expected)]
    Mismatch(Mismatch),
    /// A map key-value cut error.
    #[error("Mismatch(expected {})", .0.expected)]
    MapCut(Mismatch),
    /// A CDDL rule lookup failed.
    #[error("MissingRule({0})")]
    MissingRule(String),
    /// A CDDL feature that is unsupported.
    #[error("Unsupported {0}")]
    Unsupported(String),
    /// A data value that can't be validated by CDDL.
    #[error("ValueError({0})")]
    ValueError(String),
    /// A generic type parameter was used incorrectly.
    #[error("GenericError")]
    GenericError,
}

impl ValidateError {
    /// Identify whether this error is fatal
    ///
    /// A "fatal" error is one that should fail the entire validation, even if
    /// it occurs inside a choice or occurrence that might otherwise succeed.
    pub(crate) fn is_fatal(&self) -> bool {
        !matches!(self, ValidateError::Mismatch(_) | ValidateError::MapCut(_))
    }

    /// Convert a MapCut error to a Mismatch error; otherwise return the original error.
    pub(crate) fn erase_mapcut(self) -> ValidateError {
        match self {
            ValidateError::MapCut(m) => ValidateError::Mismatch(m),
            _ => self,
        }
    }

    pub(crate) fn is_mismatch(&self) -> bool {
        matches!(self, ValidateError::Mismatch(_))
    }
}

/// A data mismatch during validation.
///
/// If the CDDL specified an `int` and the data contained a string, this is
/// the error that would result.
#[derive(Debug, PartialEq)]
pub struct Mismatch {
    expected: String,
}

/// Shortcut for creating mismatch errors.
#[doc(hidden)]
pub fn mismatch<E: Into<String>>(expected: E) -> ValidateError {
    ValidateError::Mismatch(Mismatch {
        expected: expected.into(),
    })
}

/// A validation that doesn't return anything.
pub type ValidateResult = Result<(), ValidateError>;

// Some utility functions that are helpful when testing whether the right
// error was returned.
#[doc(hidden)]
pub trait ErrorMatch {
    fn err_mismatch(&self);
    fn err_missing_rule(&self);
    fn err_generic(&self);
    fn err_parse(&self);
    fn err_structural(&self);
}

impl ErrorMatch for ValidateResult {
    #[track_caller]
    fn err_mismatch(&self) {
        match self {
            Err(ValidateError::Mismatch(_)) => (),
            _ => panic!("expected Mismatch, got {:?}", self),
        }
    }

    #[track_caller]
    fn err_missing_rule(&self) {
        match self {
            Err(ValidateError::MissingRule(_)) => (),
            _ => panic!("expected MissingRule, got {:?}", self),
        }
    }

    #[track_caller]
    fn err_generic(&self) {
        match self {
            Err(ValidateError::GenericError) => (),
            _ => panic!("expected GenericError, got {:?}", self),
        }
    }

    #[track_caller]
    fn err_parse(&self) {
        match self {
            Err(ValidateError::ParseError(_)) => (),
            _ => panic!("expected ParseError, got {:?}", self),
        }
    }

    #[track_caller]
    fn err_structural(&self) {
        match self {
            Err(ValidateError::Structural(_)) => (),
            _ => panic!("expected Structural, got {:?}", self),
        }
    }
}

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

    #[test]
    fn test_error_extras() {
        let e: ValidateError = mismatch("");
        assert!(!e.is_fatal());

        let e = ValidateError::Structural("".into());
        assert!(e.is_fatal());
    }
}