use crate::guard::{Denial, GateSet, Receipt};
use crate::store::StoreError;
pub mod bypass;
pub use bypass::{BypassAudit, BypassReason, BypassReceipt};
pub struct Proposal<T>(
pub(crate) T,
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Committed<T> {
payload: T,
metadata: CommitMetadata,
bypass_audit: Option<BypassAudit>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CommitMetadata {
event_id: u128,
sequence: u64,
hash: [u8; 32],
is_genesis: bool,
}
pub struct Pipeline<Ctx> {
gates: GateSet<Ctx>,
}
impl<T> Proposal<T> {
pub fn new(payload: T) -> Self {
Self(payload)
}
pub fn payload(&self) -> &T {
&self.0
}
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Proposal<U> {
Proposal(f(self.0))
}
}
impl<T> Committed<T> {
pub(crate) fn new(payload: T, metadata: CommitMetadata) -> Self {
Self {
payload,
metadata,
bypass_audit: None,
}
}
pub(crate) fn new_with_audit(payload: T, metadata: CommitMetadata, audit: BypassAudit) -> Self {
Self {
payload,
metadata,
bypass_audit: Some(audit),
}
}
pub fn payload(&self) -> &T {
&self.payload
}
pub fn event_id(&self) -> u128 {
self.metadata.event_id
}
pub fn sequence(&self) -> u64 {
self.metadata.sequence
}
pub fn hash(&self) -> &[u8; 32] {
&self.metadata.hash
}
pub fn bypass_audit(&self) -> Option<&BypassAudit> {
self.bypass_audit.as_ref()
}
pub fn into_payload(self) -> T {
self.payload
}
pub fn into_parts(self) -> (T, CommitMetadata, Option<BypassAudit>) {
(self.payload, self.metadata, self.bypass_audit)
}
}
impl CommitMetadata {
pub fn new(event_id: u128, sequence: u64, hash: [u8; 32]) -> Result<Self, StoreError> {
let built = Self {
event_id,
sequence,
hash,
is_genesis: false,
};
built.validate()?;
Ok(built)
}
pub const fn genesis(event_id: u128, hash: [u8; 32]) -> Self {
Self {
event_id,
sequence: 0,
hash,
is_genesis: true,
}
}
pub fn from_append_receipt(receipt: &crate::store::AppendReceipt) -> Result<Self, StoreError> {
use crate::id::EntityIdType;
Self::new(
receipt.event_id.as_u128(),
receipt.sequence,
receipt.content_hash,
)
}
pub fn validate(&self) -> Result<(), StoreError> {
if self.is_genesis && self.sequence != 0 {
return Err(StoreError::InvalidCommitMetadata {
reason: "genesis metadata must carry sequence=0".into(),
});
}
Ok(())
}
pub const fn event_id(self) -> u128 {
self.event_id
}
pub const fn sequence(self) -> u64 {
self.sequence
}
pub const fn hash(self) -> [u8; 32] {
self.hash
}
pub const fn is_genesis(self) -> bool {
self.is_genesis
}
}
impl<Ctx> Pipeline<Ctx> {
pub fn new(gates: GateSet<Ctx>) -> Self {
Self { gates }
}
pub fn evaluate<T>(&self, ctx: &Ctx, proposal: Proposal<T>) -> Result<Receipt<T>, Denial> {
self.gates.evaluate(ctx, proposal)
}
pub fn commit<T, E>(
&self,
receipt: Receipt<T>,
commit_fn: impl FnOnce(&T) -> Result<CommitMetadata, E>,
) -> Result<Committed<T>, E>
where
E: From<StoreError>,
{
let (payload, _gate_names) = receipt.into_parts();
let metadata = commit_fn(&payload)?;
metadata.validate().map_err(E::from)?;
Ok(Committed::new(payload, metadata))
}
pub fn bypass<T>(proposal: Proposal<T>, reason: &'static dyn BypassReason) -> BypassReceipt<T> {
BypassReceipt {
payload: proposal.0,
reason: reason.name(),
justification: reason.justification(),
approved_by: None,
}
}
pub fn commit_bypass<T, E>(
receipt: BypassReceipt<T>,
commit_fn: impl FnOnce(&T) -> Result<CommitMetadata, E>,
) -> Result<Committed<T>, E>
where
E: From<StoreError>,
{
let audit = receipt.to_audit();
let payload = receipt.into_payload();
let metadata = commit_fn(&payload)?;
metadata.validate().map_err(E::from)?;
Ok(Committed::new_with_audit(payload, metadata, audit))
}
}