pub mod error;
pub use error::Error;
use crate::{Item, action, common::error::AnyResult, item};
pub use spru_macro::{Create, Destroy, Update};
use tagset::tagset_meta;
#[doc(hidden)]
pub trait SubAction {
type Undo;
type T;
fn apply<Storage>(&self, context: Context<'_, Storage>) -> action::Result<Option<Self::Undo>>
where
Storage: item::Storage,
Self::T: item::storage::Storable<Storage::State>;
fn apply_map<Storage, Action>(
&self,
context: Context<'_, Storage>,
) -> action::Result<Option<Action>>
where
Storage: item::Storage,
Self::T: item::storage::Storable<Storage::State>,
Self::Undo: Into<Action>,
{
self.apply(context)
.map(|o| o.map(Into::into))
.map_err(|e| e.with_context(self))
}
}
#[telety::telety(crate::action, alias_traits = "always")]
#[tagset_meta]
#[meta(bounds(
for<VAR> VAR:
crate::action::SubAction<Undo: Into<Self>>,
))]
pub trait Action: Sized {
type State: crate::State;
#[doc(hidden)]
#[meta(default {
match_by_value!(self, v => spru::action::SubAction::apply_map(v, context))
})]
fn apply<Storage: item::Storage<State = Self::State>>(
&self,
context: Context<'_, Storage>,
) -> action::Result<Option<Self>>;
}
pub trait Create {
type T;
type Undo;
fn create(&self) -> AnyResult<(Self::T, Self::Undo)>;
}
pub trait Update {
type T;
type Undo;
fn update(&self, value: &mut Self::T) -> AnyResult<impl Into<Option<Self::Undo>>>;
}
pub trait Destroy {
type T;
type Undo;
fn destroy(&self, value: Self::T) -> AnyResult<Self::Undo>;
}
#[doc(hidden)]
#[derive(Debug)]
pub struct Context<'l, Storage> {
pub(crate) storage: &'l mut Storage,
pub(crate) id: item::Id,
pub(crate) version: item::version::Change,
}
impl<'l, Storage> Context<'l, Storage> {
pub(crate) fn new(
storage: &'l mut Storage,
id: item::Id,
version: item::version::Change,
) -> Self {
Self {
storage,
id,
version,
}
}
#[doc(hidden)]
pub fn create<C>(self, c: &C) -> action::Result<Option<C::Undo>>
where
Storage: item::Storage,
C: Create<T: item::storage::Storable<Storage::State>>,
{
let Self {
storage,
id,
version,
} = self;
if let Ok(item) = storage.get(item::IdT::<C::T>::new(id)) {
Err(action::Error::new_item(item::Error::already_exists(
id,
item.version(),
)))
} else {
let (value, undo) = c.create()?;
let item = Item::new(item::IdT::new(id), version.after, value);
storage.create(item)?;
Ok(Some(undo))
}
}
#[doc(hidden)]
pub fn update<U>(self, u: &U) -> action::Result<Option<U::Undo>>
where
Storage: item::storage::Storage,
U: Update<T: item::storage::Storable<Storage::State>>,
{
let Self {
storage,
id,
version,
} = self;
let id = item::IdT::<U::T>::new(id);
let mut value = storage.get_mut(id).map_err(|e| e.with_context(id))?;
if version.before == (*value).version() {
let undo = u.update(value.get_mut()).map(Into::into)?;
if undo.is_some() {
(*value).set_version(version.after);
}
Ok(undo)
} else {
Err(action::Error::new_item(item::Error::wrong_version(
id.untyped(),
version.before,
value.version(),
)))
}
}
#[doc(hidden)]
pub fn destroy<D>(self, d: &D) -> action::Result<Option<D::Undo>>
where
Storage: item::storage::Storage,
D: Destroy<T: item::storage::Storable<Storage::State>>,
{
let Self {
storage,
id,
version,
} = self;
let id = item::IdT::<D::T>::new(id);
let item = storage.get(id).map_err(|e| e.with_context(id))?;
if version.before == item.version() {
let item = storage.destroy(id)?;
let undo = d.destroy(item.into_value())?;
Ok(Some(undo))
} else {
Err(action::Error::new_item(item::Error::wrong_version(
id.untyped(),
version.before,
item.version(),
)))
}
}
}
pub type Result<T> = std::result::Result<T, self::Error>;