lunar-lib 0.6.1

Common utilities for lunar applications
Documentation
use std::borrow::Borrow;

use serde::{Deserialize, Serialize, de::DeserializeOwned};
use sled::Tree;

use crate::database::{
    CompareAndSwapTransaction, CustomTransactionError, Database, DatabaseError, EntryId,
    TransactionError, deserialize_from_ivec, sled_get_all_raw, sled_get_batch_raw, sled_get_raw,
    transaction_args::db_transaction,
};

/// Defines an item that can be stored in the database
///
/// # Warning
///
/// Implementors of this trait must define a `version: u32` field, or else deserializing will panic if the value cannot be deserialized!
///
/// # Notes
///
/// For structs that should not be able to be written to the database manually, you can define [`read_only()`] function to return [`true`]
/// For structs where you want to do special logic to them before they are upserted, you can define [`pre_upsert()`]
pub trait DatabaseEntry:
    Serialize + DeserializeOwned + for<'de> Deserialize<'de> + Clone + std::fmt::Debug + 'static
{
    type EntryDb: Database;
    type Id: EntryId<Entry = Self, IdDb = Self::EntryDb>;

    /// The current version number of the struct, used for updating
    const VERSION_NUMBER: u32;

    /// Checks if an item exists in the database as lightly as possible
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to read the entry at the `id`
    fn db_check(id: Self::Id) -> Result<bool, DatabaseError> {
        let db = Self::EntryDb::open()?;
        Ok(Self::tree(&db).contains_key(&*id)?)
    }

    /// Gets the entry of [`Self`] with the given `id` from the input `db`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve the entry
    fn db_get_from(id: Self::Id, db: &Self::EntryDb) -> Result<Option<Self>, DatabaseError> {
        Ok(sled_get_raw(&Self::tree(db), id.as_bytes())?
            .map(deserialize_from_ivec)
            .transpose()?)
    }

    /// Gets the entry of [`Self`] with the given `id`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve the entry
    fn db_get(id: Self::Id) -> Result<Option<Self>, DatabaseError> {
        let db = Self::EntryDb::open()?;
        Self::db_get_from(id, &db)
    }

    /// Gets all entries of [`Self`] in from the input `db`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve any entry
    fn db_get_all_from(db: &Self::EntryDb) -> Result<Vec<Self>, DatabaseError> {
        sled_get_all_raw(&Self::tree(db))?
            .into_iter()
            .map(deserialize_from_ivec)
            .collect()
    }

    /// Gets all entries of [`Self`] in the database
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve any entry
    fn db_get_all() -> Result<Vec<Self>, DatabaseError> {
        let db = Self::EntryDb::open()?;
        Self::db_get_all_from(&db)
    }

    /// Gets all entries of [`Self`] in the database with the matching `ids` from the input `db`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve any entry
    fn db_get_batch_from<I, A>(ids: I, db: &Self::EntryDb) -> Result<Vec<Self>, DatabaseError>
    where
        I: IntoIterator<Item = A>,
        A: Borrow<Self::Id>,
    {
        let items = sled_get_batch_raw(
            &Self::tree(db),
            ids.into_iter().map(|a| *a.borrow().as_bytes()),
        )?;

        items.into_iter().map(deserialize_from_ivec).collect()
    }

    /// Gets all entries of [`Self`] in the database with the matching `ids`
    ///
    /// # Errors
    ///
    /// Errors if [`sled`] fails to retrieve any entry
    fn db_get_batch<I, A>(ids: I) -> Result<Vec<Self>, DatabaseError>
    where
        I: IntoIterator<Item = A>,
        A: Borrow<Self::Id>,
    {
        let db = Self::EntryDb::open()?;
        Self::db_get_batch_from(ids, &db)
    }

    /// This function is called before a value is upserted, allowing you to make last moment changes before its upserted into the database, like field sorting
    fn pre_upsert(
        &mut self,
        _cas_tx: &CompareAndSwapTransaction<Self::EntryDb>,
    ) -> Result<(), TransactionError> {
        Ok(())
    }

    /// Wrapper around [`db_transaction()`] which upserts the item using [`CompareAndSwapTransaction::tx_upsert()`]
    fn db_upsert(&self) -> Result<(), TransactionError> {
        db_transaction(
            |cas_tx| {
                cas_tx
                    .tx_upsert(self.id(), Some(self.clone()))
                    .map_err(CustomTransactionError::from)
            },
            None,
            false,
        )?;
        Ok(())
    }

    /// This function is called before a value is upserted, if it returns true, an error will be returned from the transaction.
    ///
    /// If you plan on making hardcoded variants of structs that are always in the database, you can set them to read only, and ensure they exist using [`Database::pre_open()`]
    fn read_only(&self) -> bool {
        false
    }

    /// Returns the ID that represents `self`
    fn id(&self) -> Self::Id;

    /// Returns the tree that `self` belongs to
    fn tree(db: &Self::EntryDb) -> Tree;
}