Skip to main content

graph_r/
error.rs

1// Author: spicyzhug@gmail.com
2// Copyright: lituus-io, all rights reserved
3//
4//! Error taxonomy. Decoders return [`Error::Format`] (or [`Error::Version`]
5//! for a well-formed file of another format version) rather than panicking on
6//! any malformed input — the property the fuzz targets enforce — and every
7//! concurrency- or lock-related refusal has its own variant so callers can
8//! distinguish "retry later" from "corrupt file".
9
10/// Crate-wide result alias.
11pub type Result<T, E = Error> = std::result::Result<T, E>;
12
13/// All failure modes surfaced by graph-r.
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum Error {
17    /// A file or byte stream failed structural validation.
18    Format {
19        /// What was malformed and where.
20        message: String,
21    },
22
23    /// Another process holds the writer lock for this store directory.
24    Locked,
25
26    /// A bounded wait for internal capacity expired (e.g. every generation
27    /// slot is pinned by long-lived snapshots during compaction).
28    Busy {
29        /// Which capacity was exhausted.
30        message: String,
31    },
32
33    /// The on-disk state is inconsistent in a way replay cannot repair.
34    Corrupt {
35        /// What invariant was violated.
36        message: String,
37    },
38
39    /// A mutating operation was attempted on a read-only store handle.
40    ReadOnly,
41
42    /// An underlying I/O failure.
43    Io {
44        /// The originating I/O error.
45        source: std::io::Error,
46    },
47
48    /// A file written in a format version this build does not read (an
49    /// older store to rebuild, or a newer one from a later release). Its own
50    /// variant so a caller can tell "rebuild this" from "this is damaged"
51    /// without reading the message.
52    Version {
53        /// The version the file declares.
54        found: u16,
55        /// The one version this build reads and writes.
56        supported: u16,
57    },
58}
59
60impl std::fmt::Display for Error {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            Self::Format { message } => write!(f, "format error: {message}"),
64            // Keeps the `format error: unsupported version N` prefix earlier
65            // releases printed, for callers that still match on the text.
66            Self::Version { found, supported } => {
67                write!(
68                    f,
69                    "format error: unsupported version {found} (this build reads {supported})"
70                )
71            }
72            Self::Locked => f.write_str("store is locked by another writer"),
73            Self::Busy { message } => write!(f, "store is busy: {message}"),
74            Self::Corrupt { message } => write!(f, "store is corrupt: {message}"),
75            Self::ReadOnly => f.write_str("store was opened read-only"),
76            Self::Io { source } => write!(f, "io error: {source}"),
77        }
78    }
79}
80
81impl std::error::Error for Error {
82    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
83        match self {
84            Self::Io { source } => Some(source),
85            _ => None,
86        }
87    }
88}
89
90impl From<std::io::Error> for Error {
91    fn from(source: std::io::Error) -> Self {
92        Self::Io { source }
93    }
94}
95
96impl Error {
97    /// A structural-validation failure.
98    #[must_use]
99    pub fn format(message: impl Into<String>) -> Self {
100        Self::Format { message: message.into() }
101    }
102
103    /// A capacity-exhaustion refusal.
104    #[must_use]
105    pub fn busy(message: impl Into<String>) -> Self {
106        Self::Busy { message: message.into() }
107    }
108
109    /// An unrepairable inconsistency.
110    #[must_use]
111    pub fn corrupt(message: impl Into<String>) -> Self {
112        Self::Corrupt { message: message.into() }
113    }
114}