Files
db/heromodels/src/db.rs
Lee Smet 74a1215554 Proper jsonb field encoding
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
2025-07-31 12:15:52 +02:00

127 lines
4.5 KiB
Rust

use std::borrow::Borrow;
use heromodels_core::{Index, Model};
use serde::{Deserialize, Serialize};
pub mod hero;
pub mod postgres;
pub trait Db {
/// Error type returned by database operations.
type Error: std::fmt::Debug;
/// Open the collection for a specific model. This method must create the collection if it does not exist.
fn collection<M: Model>(&self) -> Result<impl Collection<&str, M>, Error<Self::Error>>;
}
/// A collection stores a specific model under a specific key
pub trait Collection<K, V>
where
K: Serialize,
V: Serialize + for<'a> Deserialize<'a>,
{
/// Error type for database operations
type Error: std::fmt::Debug;
/// Get all items where the given index field is equal to key.
fn get<I, Q>(&self, key: &Q) -> Result<Vec<V>, Error<Self::Error>>
where
I: Index<Model = V>,
I::Key: Borrow<Q>,
Q: ToString + Serialize + core::fmt::Debug + Sync + ?Sized;
/// Get an object from its ID. This does not use an index lookup
fn get_by_id(&self, id: u32) -> Result<Option<V>, Error<Self::Error>>;
/// Store an item in the DB and return the assigned ID and the updated model.
///
/// # Important Notes
/// - This method does not modify the original model passed as an argument.
/// - For new models (with ID 0), an ID will be auto-generated by OurDB.
/// - The returned model will have the correct ID and should be used instead of the original model.
/// - The original model should not be used after calling this method, as it may have
/// an inconsistent state compared to what's in the database.
/// - ID 0 is reserved for new models and should not be used for existing models.
fn set(&self, value: &V) -> Result<(u32, V), Error<Self::Error>>;
/// Delete all items from the db with a given index.
fn delete<I, Q>(&self, key: &Q) -> Result<(), Error<Self::Error>>
where
I: Index<Model = V>,
I::Key: Borrow<Q>,
Q: ToString + Serialize + core::fmt::Debug + Sync + ?Sized;
/// Delete an object with a given ID
fn delete_by_id(&self, id: u32) -> Result<(), Error<Self::Error>>;
/// Get all objects from the collection
fn get_all(&self) -> Result<Vec<V>, Error<Self::Error>>;
/// Begin a transaction for this collection
fn begin_transaction(
&self,
) -> Result<Box<dyn Transaction<Error = Self::Error>>, Error<Self::Error>>;
}
/// Errors returned by the DB implementation
#[derive(Debug)]
pub enum Error<E> {
/// Error in the underlying database
DB(E),
/// Error decoding a stored model
Decode(bincode::error::DecodeError),
/// Error encoding a model for storage
Encode(bincode::error::EncodeError),
/// Invalid ID used (e.g., using ID 0 for an existing model)
InvalidId(String),
/// ID mismatch (e.g., expected ID 5 but got ID 6)
IdMismatch(String),
/// ID collision (e.g., trying to create a model with an ID that already exists)
IdCollision(String),
/// Type error (e.g., trying to get a model of the wrong type)
TypeError,
}
impl<E> From<bincode::error::DecodeError> for Error<E> {
fn from(value: bincode::error::DecodeError) -> Self {
Error::Decode(value)
}
}
impl<E> From<bincode::error::EncodeError> for Error<E> {
fn from(value: bincode::error::EncodeError) -> Self {
Error::Encode(value)
}
}
impl<E: std::fmt::Debug + std::fmt::Display> std::fmt::Display for Error<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::DB(e) => write!(f, "Database error: {}", e),
Error::Decode(e) => write!(f, "Failed to decode model: {}", e),
Error::Encode(e) => write!(f, "Failed to encode model: {}", e),
Error::InvalidId(s) => write!(f, "Invalid ID: {}", s),
Error::IdMismatch(s) => write!(f, "ID Mismatch: {}", s),
Error::IdCollision(s) => write!(f, "ID Collision: {}", s),
Error::TypeError => write!(f, "Type error"),
}
}
}
/// A transaction that can be committed or rolled back
pub trait Transaction {
/// Error type for transaction operations
type Error: std::fmt::Debug;
/// Begin a transaction
fn begin(&self) -> Result<(), Error<Self::Error>>;
/// Commit a transaction
fn commit(&self) -> Result<(), Error<Self::Error>>;
/// Roll back a transaction
fn rollback(&self) -> Result<(), Error<Self::Error>>;
/// Check if a transaction is active
fn is_active(&self) -> bool;
}