use std::collections::BTreeMap;
use std::sync::Arc;
use std::{error::Error, fmt};
use batpak::event::{EventKind, EventPayload};
use batpak::store::{EncodedBytes, ExtensionKey};
use serde::{Deserialize, Serialize};
use crate::operation::OperationDescriptor;
pub const SYNCBAT_RECEIPT_EVENT_KIND: EventKind = EventKind::custom(0xC, 0x5B7);
pub type ReceiptHash = [u8; 32];
pub trait ReceiptHasher {
fn hash(&self, bytes: &[u8]) -> ReceiptHash;
}
impl<F> ReceiptHasher for F
where
F: Fn(&[u8]) -> ReceiptHash,
{
fn hash(&self, bytes: &[u8]) -> ReceiptHash {
self(bytes)
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub enum ReceiptHashPolicy {
#[default]
Deferred,
RawBytes(Arc<dyn ReceiptHasher>),
}
impl ReceiptHashPolicy {
#[must_use]
pub fn raw_bytes(hasher: impl ReceiptHasher + 'static) -> Self {
Self::RawBytes(Arc::new(hasher))
}
#[must_use]
pub fn hash(&self, bytes: &[u8]) -> Option<ReceiptHash> {
match self {
Self::Deferred => None,
Self::RawBytes(hasher) => Some(hasher.hash(bytes)),
}
}
}
pub type ReceiptExtensionDrawer = BTreeMap<String, Vec<u8>>;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub enum ReceiptOutcome {
Completed,
Failed {
code: String,
message: String,
},
Denied {
code: String,
message: String,
},
}
impl ReceiptOutcome {
#[must_use]
pub fn failed(code: impl Into<String>, message: impl Into<String>) -> Self {
Self::Failed {
code: code.into(),
message: message.into(),
}
}
#[must_use]
pub fn denied(code: impl Into<String>, message: impl Into<String>) -> Self {
Self::Denied {
code: code.into(),
message: message.into(),
}
}
#[must_use]
pub const fn class(&self) -> &'static str {
match self {
Self::Completed => "completed",
Self::Failed { .. } => "failed",
Self::Denied { .. } => "denied",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct BatpakReceiptFields {
pub event_id: batpak::id::EventId,
pub sequence: u64,
pub content_hash: ReceiptHash,
pub key_id: ReceiptHash,
pub signature: Option<[u8; 64]>,
pub extensions: BTreeMap<ExtensionKey, EncodedBytes>,
}
impl From<batpak::store::AppendReceipt> for BatpakReceiptFields {
fn from(receipt: batpak::store::AppendReceipt) -> Self {
Self {
event_id: receipt.event_id,
sequence: receipt.sequence,
content_hash: receipt.content_hash,
key_id: receipt.key_id,
signature: receipt.signature,
extensions: receipt.extensions,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ReceiptEnvelope {
pub descriptor_name: String,
pub receipt_kind: String,
pub input_hash: Option<ReceiptHash>,
pub output_hash: Option<ReceiptHash>,
pub outcome: ReceiptOutcome,
pub signed_extensions: ReceiptExtensionDrawer,
pub local_extensions: ReceiptExtensionDrawer,
}
impl ReceiptEnvelope {
#[must_use]
pub fn new(descriptor: &OperationDescriptor, outcome: ReceiptOutcome) -> Self {
Self::from_descriptor(descriptor.name(), descriptor.receipt_kind(), outcome)
}
#[must_use]
pub fn from_descriptor(
descriptor_name: impl Into<String>,
receipt_kind: impl Into<String>,
outcome: ReceiptOutcome,
) -> Self {
Self {
descriptor_name: descriptor_name.into(),
receipt_kind: receipt_kind.into(),
input_hash: None,
output_hash: None,
outcome,
signed_extensions: BTreeMap::new(),
local_extensions: BTreeMap::new(),
}
}
#[must_use]
pub fn with_input_hash(mut self, hash: ReceiptHash) -> Self {
self.input_hash = Some(hash);
self
}
#[must_use]
pub fn with_output_hash(mut self, hash: ReceiptHash) -> Self {
self.output_hash = Some(hash);
self
}
#[must_use]
pub fn with_signed_extension(
mut self,
key: impl Into<String>,
value: impl Into<Vec<u8>>,
) -> Self {
self.signed_extensions.insert(key.into(), value.into());
self
}
#[must_use]
pub fn with_local_extension(
mut self,
key: impl Into<String>,
value: impl Into<Vec<u8>>,
) -> Self {
self.local_extensions.insert(key.into(), value.into());
self
}
}
impl EventPayload for ReceiptEnvelope {
const KIND: EventKind = SYNCBAT_RECEIPT_EVENT_KIND;
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct RecordedReceipt {
pub envelope: ReceiptEnvelope,
pub batpak_receipt: Option<BatpakReceiptFields>,
}
impl RecordedReceipt {
#[must_use]
pub fn new(envelope: ReceiptEnvelope) -> Self {
Self {
envelope,
batpak_receipt: None,
}
}
#[must_use]
pub fn with_batpak_receipt(mut self, receipt: impl Into<BatpakReceiptFields>) -> Self {
self.batpak_receipt = Some(receipt.into());
self
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ReceiptSinkError {
message: String,
}
impl ReceiptSinkError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for ReceiptSinkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl Error for ReceiptSinkError {}
pub trait ReceiptSink {
fn record_receipt(
&self,
envelope: &ReceiptEnvelope,
) -> Result<RecordedReceipt, ReceiptSinkError>;
}