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}");
}
}