use core::{fmt, iter::DoubleEndedIterator};
use buggy::Bug;
use tracing::error;
use crate::{
Address, CmdId, Command, GraphId, PeerCache, Perspective as _, Policy, PolicyError,
PolicyStore, Sink, Storage as _, StorageError, StorageProvider, TraversalBuffer,
policy::ActionPlacement,
};
mod braiding;
mod session;
mod transaction;
pub use self::{session::Session, transaction::Transaction};
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error("no such parent: {0}")]
NoSuchParent(CmdId),
#[error("policy error: {0}")]
PolicyError(PolicyError),
#[error("storage error: {0}")]
StorageError(#[from] StorageError),
#[error("init error")]
InitError,
#[error("not authorized")]
NotAuthorized,
#[error("could not deserialize session command")]
SessionDeserialize,
#[error("found parallel finalize commands during braid")]
ParallelFinalize,
#[error("concurrent transaction usage")]
ConcurrentTransaction,
#[error(transparent)]
Bug(#[from] Bug),
}
impl From<PolicyError> for ClientError {
fn from(error: PolicyError) -> Self {
match error {
PolicyError::Check => Self::NotAuthorized,
_ => Self::PolicyError(error),
}
}
}
pub struct ClientState<PS, SP> {
policy_store: PS,
provider: SP,
}
impl<PS: fmt::Debug, SP: fmt::Debug> fmt::Debug for ClientState<PS, SP> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClientState")
.field("policy_store", &self.policy_store)
.field("provider", &self.provider)
.finish_non_exhaustive()
}
}
impl<PS, SP> ClientState<PS, SP> {
pub const fn new(policy_store: PS, provider: SP) -> Self {
Self {
policy_store,
provider,
}
}
pub fn provider(&mut self) -> &mut SP {
&mut self.provider
}
}
impl<PS, SP> ClientState<PS, SP>
where
PS: PolicyStore,
SP: StorageProvider,
{
pub fn new_graph(
&mut self,
policy_data: &[u8],
action: <PS::Policy as Policy>::Action<'_>,
sink: &mut impl Sink<PS::Effect>,
) -> Result<GraphId, ClientError> {
let policy_id = self.policy_store.add_policy(policy_data)?;
let policy = self.policy_store.get_policy(policy_id)?;
let mut perspective = self.provider.new_perspective(policy_id);
sink.begin();
policy
.call_action(action, &mut perspective, sink, ActionPlacement::OnGraph)
.inspect_err(|_| sink.rollback())?;
sink.commit();
let (graph_id, _) = self.provider.new_storage(perspective)?;
Ok(graph_id)
}
pub fn remove_graph(&mut self, graph_id: GraphId) -> Result<(), ClientError> {
self.provider.remove_storage(graph_id)?;
Ok(())
}
pub fn commit(
&mut self,
trx: Transaction<SP, PS>,
sink: &mut impl Sink<PS::Effect>,
buffer: &mut TraversalBuffer,
) -> Result<bool, ClientError> {
trx.commit(&mut self.provider, &mut self.policy_store, sink, buffer)
}
pub fn add_commands(
&mut self,
trx: &mut Transaction<SP, PS>,
sink: &mut impl Sink<PS::Effect>,
commands: &[impl Command],
buffer: &mut TraversalBuffer,
) -> Result<usize, ClientError> {
trx.add_commands(
commands,
&mut self.provider,
&mut self.policy_store,
sink,
buffer,
)
}
pub fn update_heads<I>(
&mut self,
graph_id: GraphId,
addrs: I,
request_heads: &mut PeerCache,
buffer: &mut TraversalBuffer,
) -> Result<(), ClientError>
where
I: IntoIterator<Item = Address>,
I::IntoIter: DoubleEndedIterator,
{
let storage = self.provider.get_storage(graph_id)?;
for address in addrs.into_iter().rev() {
if let Some(loc) = storage.get_location(address, buffer)? {
request_heads.add_command(storage, address, loc, buffer)?;
} else {
error!(
"UPDATE_HEADS: Address {:?} does NOT exist in storage, skipping (should not happen if command was successfully added)",
address
);
}
}
Ok(())
}
pub fn head_address(&mut self, graph_id: GraphId) -> Result<Address, ClientError> {
let storage = self.provider.get_storage(graph_id)?;
let address = storage.get_head_address()?;
Ok(address)
}
pub fn action(
&mut self,
graph_id: GraphId,
sink: &mut impl Sink<PS::Effect>,
action: <PS::Policy as Policy>::Action<'_>,
) -> Result<(), ClientError> {
let storage = self.provider.get_storage(graph_id)?;
let head = storage.get_head()?;
let mut perspective = storage.get_linear_perspective(head)?;
let policy_id = perspective.policy();
let policy = self.policy_store.get_policy(policy_id)?;
sink.begin();
match policy.call_action(action, &mut perspective, sink, ActionPlacement::OnGraph) {
Ok(()) => {
let segment = storage.write(perspective)?;
storage.commit(segment)?;
sink.commit();
Ok(())
}
Err(e) => {
sink.rollback();
Err(e.into())
}
}
}
}
impl<PS, SP> ClientState<PS, SP>
where
SP: StorageProvider,
{
pub fn transaction(&mut self, graph_id: GraphId) -> Transaction<SP, PS> {
Transaction::new(graph_id)
}
pub fn session(&mut self, graph_id: GraphId) -> Result<Session<SP, PS>, ClientError> {
Session::new(&mut self.provider, graph_id)
}
pub fn command_exists(
&mut self,
graph_id: GraphId,
address: Address,
buffer: &mut TraversalBuffer,
) -> bool {
let Ok(storage) = self.provider.get_storage(graph_id) else {
return false;
};
storage
.get_location(address, buffer)
.unwrap_or(None)
.is_some()
}
}