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>;