Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-server/src/store.rs

//! **`StoreError`** — what the server's own redb stores (usage, inbox, cases,
//! signups) answer when the disk or a row says no.
//!
//! It is `redb::Error`, boxed: that error is 160 bytes, and every `Result` a
//! store returns would be that wide on its happy path too. Every redb error kind
//! converts with `?`, and it reads and chains exactly as the error inside it.

/// A redb store error, boxed. See the module docs.
pub struct StoreError(Box<redb::Error>);

impl StoreError {
    /// A row that is there but will not parse: `redb::Error::Corrupted`.
    pub fn corrupted(what: impl Into<String>) -> Self {
        redb::Error::Corrupted(what.into()).into()
    }
}

impl StoreError {
    /// **A row's bytes, or the store error that says why there are none** (R5a):
    /// the stores encoded with `serde_json::to_vec(..).expect(..)`, a panic in
    /// the request handler that wrote the row. `what` names the row.
    pub fn encode<T: serde::Serialize + ?Sized>(
        what: &str,
        row: &T,
    ) -> Result<Vec<u8>, StoreError> {
        serde_json::to_vec(row).map_err(|e| {
            redb::Error::Io(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("{what} does not encode: {e}"),
            ))
            .into()
        })
    }
}

impl std::fmt::Display for StoreError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Display::fmt(&self.0, f)
    }
}

impl std::fmt::Debug for StoreError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Debug::fmt(&self.0, f)
    }
}

impl std::error::Error for StoreError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.0.source()
    }
}

macro_rules! from_redb {
    ($($e:ty),*) => {$(
        impl From<$e> for StoreError {
            fn from(e: $e) -> Self { StoreError(Box::new(e.into())) }
        }
    )*};
}
from_redb!(
    redb::Error,
    redb::DatabaseError,
    redb::TransactionError,
    redb::TableError,
    redb::StorageError,
    redb::CommitError
);

/// R5a: a row that cannot be encoded is a store error the caller answers with,
/// never a panic in a request handler.
#[cfg(test)]
mod encode_tests {
    use super::*;

    #[test]
    fn a_row_that_cannot_be_encoded_is_a_store_error() {
        let fine =
            StoreError::encode("a row", &serde_json::json!({"a": 1})).expect("a plain row encodes");
        assert_eq!(fine, br#"{"a":1}"#);
        // serde_json refuses a map whose keys are not strings.
        let bad: std::collections::BTreeMap<(u8, u8), u8> = [((1, 2), 3)].into();
        let e = StoreError::encode("a signup record", &bad)
            .expect_err("a map with tuple keys does not encode");
        assert!(e.to_string().contains("a signup record"), "{e}");
    }
}