graven/
validate.rs

1//! Results of the validation rules declared in a schema (`range`, `len`, `finite`, `known`,
2//! `check(name)`).
3//!
4//! Validation is separate from `verify`: `verify` checks that bytes form a well-formed
5//! message, while validation checks that its values follow the schema's rules. Generated code
6//! exposes it as `XVerified::validate(&checks)` and `XW::to_bytes_validated`. Reading never
7//! runs it implicitly, so data written under older rules stays readable.
8
9use core::fmt;
10
11use crate::Error;
12
13/// The rule a value broke.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Rule {
16    /// `range(min = …, max = …)`: a number outside its bounds (NaN is always outside).
17    Range,
18    /// `len(min = …, max = …)`: a string or `bytes` length (in bytes), or a list's element
19    /// count, outside its bounds.
20    Len,
21    /// `finite`: a float that is NaN or infinite.
22    Finite,
23    /// `known`: an enum value the reader's schema does not declare.
24    Known,
25    /// `check(name)`: the application's function `name` returned `false`.
26    Check(&'static str),
27}
28
29impl fmt::Display for Rule {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Rule::Range => f.write_str("range"),
33            Rule::Len => f.write_str("len"),
34            Rule::Finite => f.write_str("finite"),
35            Rule::Known => f.write_str("known"),
36            Rule::Check(name) => write!(f, "check({name})"),
37        }
38    }
39}
40
41/// A value that breaks a rule of the schema.
42///
43/// `table` and `field` name where the rule is declared (`field` is `None` for a check on the
44/// whole table). `index` is the position in the nearest enclosing list: the element that
45/// failed in a list of enums, or the element of a list of tables in which the failing value
46/// sits.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct Invalid {
49    pub table: &'static str,
50    pub field: Option<&'static str>,
51    pub index: Option<u32>,
52    pub rule: Rule,
53}
54
55impl fmt::Display for Invalid {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.write_str(self.table)?;
58        if let Some(field) = self.field {
59            write!(f, ".{field}")?;
60        }
61        if let Some(i) = self.index {
62            write!(f, "[{i}]")?;
63        }
64        write!(f, ": breaks rule `{}`", self.rule)
65    }
66}
67
68impl core::error::Error for Invalid {}
69
70/// The error of `validate` and `to_bytes_validated`: a rule was broken, or the message could
71/// not be read or written.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum ValidateError {
74    Invalid(Invalid),
75    Read(Error),
76}
77
78impl ValidateError {
79    /// Records the list position `i` on a failure that does not have one yet (used by
80    /// generated code when it descends into the elements of a list).
81    #[must_use]
82    pub fn at(self, i: usize) -> Self {
83        match self {
84            ValidateError::Invalid(mut v) if v.index.is_none() => {
85                v.index = u32::try_from(i).ok();
86                ValidateError::Invalid(v)
87            }
88            other => other,
89        }
90    }
91}
92
93impl From<Invalid> for ValidateError {
94    fn from(v: Invalid) -> Self {
95        ValidateError::Invalid(v)
96    }
97}
98
99impl From<Error> for ValidateError {
100    fn from(e: Error) -> Self {
101        ValidateError::Read(e)
102    }
103}
104
105impl fmt::Display for ValidateError {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            ValidateError::Invalid(v) => v.fmt(f),
109            ValidateError::Read(e) => e.fmt(f),
110        }
111    }
112}
113
114impl core::error::Error for ValidateError {}