Skip to main content

kaspa_database/
errors.rs

1use crate::prelude::DbKey;
2use kaspa_hashes::Hash;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum StoreError {
7    #[error("key {0} not found in store")]
8    KeyNotFound(DbKey),
9
10    #[error("key {0} already exists in store")]
11    KeyAlreadyExists(String),
12
13    /// Specialization of key already exists for the common `Hash` case.
14    /// Added for avoiding the `String` allocation
15    #[error("hash {0} already exists in store")]
16    HashAlreadyExists(Hash),
17
18    #[error("data inconsistency: {0}")]
19    DataInconsistency(String),
20
21    #[error("rocksdb error {0}")]
22    DbError(#[from] rocksdb::Error),
23
24    #[error("bincode error {0}")]
25    DeserializationError(#[from] Box<bincode::ErrorKind>),
26}
27
28pub type StoreResult<T> = std::result::Result<T, StoreError>;
29
30/// Predicates that classify store errors into common semantic buckets.
31///
32/// This is used by result extension methods (e.g. `optional`, `idempotent`)
33/// to treat certain expected error conditions as benign outcomes.
34pub trait StoreErrorPredicates {
35    /// Returns `true` if this error represents a missing entry (e.g. key not found).
36    fn is_key_not_found(&self) -> bool;
37
38    /// Returns `true` if this error represents a duplicate write (e.g. key/hash already exists).
39    fn is_already_exists(&self) -> bool;
40}
41
42impl StoreErrorPredicates for StoreError {
43    fn is_key_not_found(&self) -> bool {
44        matches!(self, StoreError::KeyNotFound(_))
45    }
46
47    fn is_already_exists(&self) -> bool {
48        matches!(self, StoreError::KeyAlreadyExists(_) | StoreError::HashAlreadyExists(_))
49    }
50}
51
52/// Extension methods for store results.
53pub trait StoreResultExt<T, E: StoreErrorPredicates> {
54    /// Converts a "key not found" error into absence.
55    ///
56    /// Mapping:
57    /// - `Ok(v)` -> `Ok(Some(v))`
58    /// - `Err(e)` where `e.is_key_not_found()` -> `Ok(None)`
59    /// - any other `Err(e)` -> `Err(e)`
60    ///
61    /// This method does **not** panic.
62    fn optional(self) -> Result<Option<T>, E>;
63}
64
65impl<T, E: StoreErrorPredicates> StoreResultExt<T, E> for Result<T, E> {
66    fn optional(self) -> Result<Option<T>, E> {
67        match self {
68            Ok(value) => Ok(Some(value)),
69            Err(err) if err.is_key_not_found() => Ok(None),
70            Err(err) => Err(err),
71        }
72    }
73}
74
75/// Extension methods for unit (`()`) store results, typically produced by write operations.
76pub trait StoreResultUnitExt<E: StoreErrorPredicates> {
77    /// Treats a duplicate-write error as success, making the operation idempotent.
78    ///
79    /// Mapping:
80    /// - `Ok(())` -> `Ok(())`
81    /// - `Err(e)` where `e.is_already_exists()` -> `Ok(())`
82    /// - any other `Err(e)` -> `Err(e)`
83    ///
84    /// This method does **not** panic.
85    fn idempotent(self) -> Result<(), E>;
86}
87
88impl<E: StoreErrorPredicates> StoreResultUnitExt<E> for Result<(), E> {
89    fn idempotent(self) -> Result<(), E> {
90        match self {
91            Ok(()) => Ok(()),
92            Err(err) if err.is_already_exists() => Ok(()),
93            Err(err) => Err(err),
94        }
95    }
96}