#[cfg(feature = "alloc")]
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{borrow::ToOwned, string::ToString, vec, vec::Vec};
use core::marker::PhantomData;
use crate::core::primitives::{Saider, Seqner};
use crate::keri::{Identifier, InteractionEvent, Seal};
use super::icp::dummy_saider;
use crate::serder::error::SerderError;
use crate::serder::serialize::SerializedEvent;
pub struct NeedsPrefix;
pub struct NeedsPriorSaid;
pub struct Ready;
#[must_use]
pub struct InteractionBuilder<State = NeedsPrefix> {
prefix: Option<Identifier<'static>>,
prior_event_said: Option<Saider<'static>>,
sn: Option<u128>,
anchors: Vec<Seal>,
_state: PhantomData<State>,
}
impl InteractionBuilder<NeedsPrefix> {
pub const fn new() -> Self {
Self {
prefix: None,
prior_event_said: None,
sn: None,
anchors: Vec::new(),
_state: PhantomData,
}
}
pub fn prefix(
self,
prefix: impl Into<Identifier<'static>>,
) -> InteractionBuilder<NeedsPriorSaid> {
InteractionBuilder {
prefix: Some(prefix.into()),
prior_event_said: self.prior_event_said,
sn: self.sn,
anchors: self.anchors,
_state: PhantomData,
}
}
}
impl Default for InteractionBuilder<NeedsPrefix> {
fn default() -> Self {
Self::new()
}
}
impl InteractionBuilder<NeedsPriorSaid> {
pub fn prior_event_said(self, said: Saider<'static>) -> InteractionBuilder<Ready> {
InteractionBuilder {
prefix: self.prefix,
prior_event_said: Some(said),
sn: self.sn,
anchors: self.anchors,
_state: PhantomData,
}
}
}
impl InteractionBuilder<Ready> {
pub const fn sn(mut self, sn: u128) -> Self {
self.sn = Some(sn);
self
}
pub fn anchors(mut self, anchors: Vec<Seal>) -> Self {
self.anchors = anchors;
self
}
pub fn build(self) -> Result<SerializedEvent, SerderError> {
let sn = self.sn.unwrap_or(1);
if sn == 0 {
return Err(SerderError::Validation(
"interaction sn must be >= 1".to_owned(),
));
}
let prefix = self
.prefix
.ok_or_else(|| SerderError::Validation("prefix is required".to_owned()))?;
let prior_event_said = self
.prior_event_said
.ok_or_else(|| SerderError::Validation("prior_event_said is required".to_owned()))?;
let event = InteractionEvent::new(
prefix,
Seqner::new(sn),
dummy_saider()?,
prior_event_said,
self.anchors,
);
crate::serder::serialize::ixn::serialize_interaction(&event)
}
}
#[cfg(test)]
#[allow(clippy::panic, reason = "panics are expected in test assertions")]
mod tests {
use alloc::borrow::Cow;
use crate::core::matter::builder::MatterBuilder;
use crate::core::matter::code::{DigestCode, VerKeyCode};
use crate::core::primitives::{Prefixer, Saider};
use super::*;
fn make_prefixer() -> Prefixer<'static> {
MatterBuilder::new()
.with_code(VerKeyCode::Ed25519)
.with_raw(Cow::<[u8]>::Owned(vec![3u8; 32]))
.unwrap()
.build()
.unwrap()
}
fn make_saider() -> Saider<'static> {
MatterBuilder::new()
.with_code(DigestCode::Blake3_256)
.with_raw(Cow::<[u8]>::Owned(vec![4u8; 32]))
.unwrap()
.build()
.unwrap()
}
#[test]
fn build_minimal_interaction() {
let result = InteractionBuilder::new()
.prefix(make_prefixer())
.prior_event_said(make_saider())
.build()
.unwrap();
assert_eq!(result.ilk(), crate::keri::Ilk::Ixn);
let parsed: serde_json::Value = serde_json::from_slice(result.as_bytes()).unwrap();
assert_eq!(parsed["t"].as_str().unwrap(), "ixn");
assert_eq!(parsed["s"].as_str().unwrap(), "1");
}
#[test]
fn build_with_all_options() {
let result = InteractionBuilder::new()
.prefix(make_prefixer())
.prior_event_said(make_saider())
.sn(5)
.anchors(vec![Seal::Digest { d: make_saider() }])
.build()
.unwrap();
let parsed: serde_json::Value = serde_json::from_slice(result.as_bytes()).unwrap();
assert_eq!(parsed["t"].as_str().unwrap(), "ixn");
assert_eq!(parsed["s"].as_str().unwrap(), "5");
let a = parsed["a"].as_array().unwrap();
assert_eq!(a.len(), 1);
}
#[test]
fn roundtrip() {
let serialized = InteractionBuilder::new()
.prefix(make_prefixer())
.prior_event_said(make_saider())
.anchors(vec![Seal::Digest { d: make_saider() }])
.build()
.unwrap();
let recovered =
crate::serder::deserialize::deserialize_interaction(serialized.as_bytes()).unwrap();
assert_eq!(recovered.sn().value(), 1);
assert_eq!(recovered.anchors().len(), 1);
}
#[test]
fn sn_zero_rejected() {
let result = InteractionBuilder::new()
.prefix(make_prefixer())
.prior_event_said(make_saider())
.sn(0)
.build();
let Err(err) = result else {
panic!("expected error");
};
assert!(err.to_string().contains("sn must be >= 1"));
}
#[test]
fn build_interaction_with_self_addressing_prefix() {
let result = InteractionBuilder::new()
.prefix(make_saider())
.prior_event_said(make_saider())
.build()
.unwrap();
assert_eq!(result.ilk(), crate::keri::Ilk::Ixn);
let parsed =
crate::serder::deserialize::deserialize_interaction(result.as_bytes()).unwrap();
assert!(
parsed.prefix().as_saider().is_some(),
"interaction prefix must decode as self-addressing"
);
}
#[test]
fn default_impl() {
let builder = InteractionBuilder::default();
let result = builder
.prefix(make_prefixer())
.prior_event_said(make_saider())
.build()
.unwrap();
assert_eq!(result.ilk(), crate::keri::Ilk::Ixn);
}
}