Skip to main content

iris_guard/
error.rs

1//! What the guard refuses, and the rule it refused under.
2
3use core::fmt;
4
5/// The rule an array broke.
6///
7/// This is an enum rather than a string because a host that wants to count refusals by kind, or
8/// treat one kind differently from another, should not have to match on prose. The prose is in the
9/// detail, which is written for whoever has to go and find the decoder that produced this.
10#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
11#[non_exhaustive]
12pub enum Invariant {
13    /// The schema nests deeper than this crate will walk.
14    Depth,
15    /// The type is one this crate does not know how to check.
16    Unsupported,
17    /// The batch does not have the number of arrays the schema calls for.
18    Arrays,
19    /// The batch does not have the number of buffers the schema calls for.
20    Buffers,
21    /// A top level array is not as long as the batch says it is.
22    Rows,
23    /// A child array is shorter than its parent needs it to be.
24    ChildLength,
25    /// The declared null count is not what the validity bitmap says.
26    NullCount,
27    /// The validity bitmap has fewer bits than the array has slots.
28    Validity,
29    /// A buffer is shorter than the array's length requires.
30    BufferLength,
31    /// A length and a width multiply to more than this host can address.
32    Size,
33    /// Offsets run backwards.
34    OffsetOrder,
35    /// An offset points past the end of the thing it indexes.
36    OffsetRange,
37    /// A dictionary key is not a slot in the dictionary.
38    DictionaryIndex,
39    /// A view points at a data buffer that is not there, or past the end of one that is.
40    ViewBuffer,
41}
42
43impl Invariant {
44    /// The rule's name, as it appears in a message.
45    #[must_use]
46    pub const fn name(self) -> &'static str {
47        match self {
48            Self::Depth => "nesting depth",
49            Self::Unsupported => "known types",
50            Self::Arrays => "array count",
51            Self::Buffers => "buffer count",
52            Self::Rows => "row count",
53            Self::ChildLength => "child length",
54            Self::NullCount => "null count",
55            Self::Validity => "validity length",
56            Self::BufferLength => "buffer length",
57            Self::Size => "addressable size",
58            Self::OffsetOrder => "offset order",
59            Self::OffsetRange => "offset range",
60            Self::DictionaryIndex => "dictionary index",
61            Self::ViewBuffer => "view buffer",
62        }
63    }
64}
65
66impl fmt::Display for Invariant {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.write_str(self.name())
69    }
70}
71
72/// An array the guard will not let through.
73///
74/// The three fields are the three questions somebody debugging this asks in order: which rule, where
75/// in the batch, and what the numbers were. A message that answers only the first is the reason
76/// people stop reading error messages.
77#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
78#[error("{path} breaks the {invariant} rule: {detail}")]
79pub struct Violation {
80    /// The rule that was broken.
81    pub invariant: Invariant,
82    /// Where in the batch, as a dotted path of field names.
83    pub path: String,
84    /// The numbers, for whoever has to fix the decoder.
85    pub detail: String,
86}
87
88impl Violation {
89    /// A violation at a path.
90    pub(crate) fn at(invariant: Invariant, path: &str, detail: impl Into<String>) -> Self {
91        Self {
92            invariant,
93            path: if path.is_empty() {
94                "the batch".to_owned()
95            } else {
96                path.to_owned()
97            },
98            detail: detail.into(),
99        }
100    }
101}
102
103/// What this crate returns.
104pub type Result<T> = core::result::Result<T, Violation>;