//! Event bodies: what a consumer is handed when something happens to a //! repository. //! //! The log stores these without looking inside one, so the shapes live here //! rather than next to it. Every field is the reference's, down to the two //! that are deprecated and still required. use serde::Serialize; use crate::repo::{BlockMap, Cid, Error, Store, car, encode}; use crate::store::Event; use crate::syntax::{Did, Handle, Tid}; /// An entry built and waiting for the log. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Body { /// Which kind of entry it is. pub event: Event, /// The dag-cbor a consumer decodes. pub bytes: Vec, } /// What one commit did to one record. #[derive(Debug, Serialize)] pub struct Op { /// `create`, `update` or `delete`. pub action: &'static str, /// The collection and the record key, joined by a slash. pub path: String, /// Where the record landed, or null where it was deleted. pub cid: Option, /// What stood there before, left out entirely where nothing did. #[serde(skip_serializing_if = "Option::is_none")] pub prev: Option, } /// A handle or DID document changed. #[derive(Debug, Serialize)] struct Identity<'a> { did: &'a Did, handle: &'a Handle, } /// An account became reachable or stopped being. #[derive(Debug, Serialize)] struct Account<'a> { did: &'a Did, active: bool, } /// A commit was written. #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct Append<'a> { repo: &'a Did, commit: Cid, rev: &'a Tid, since: Option<&'a Tid>, #[serde(with = "serde_bytes")] blocks: Vec, ops: &'a [Op], /// Left out entirely when there is no revision before this one. #[serde(skip_serializing_if = "Option::is_none")] prev_data: Option, rebase: bool, too_big: bool, blobs: &'a [Cid], } /// Where a repository is now, for a consumer that fell behind. #[derive(Debug, Serialize)] struct Sync<'a> { did: &'a Did, rev: &'a Tid, #[serde(with = "serde_bytes")] blocks: Vec, } /// One commit, as the log carries it. #[derive(Debug)] pub struct Commit<'a> { /// Whose repository moved. pub did: &'a Did, /// The commit block it moved onto. pub commit: Cid, /// The revision that commit carries. pub rev: &'a Tid, /// The revision before it, absent only on the first. pub since: Option<&'a Tid>, /// The tree the revision before it held, which is what lets a consumer /// check it did not miss one. pub prev_data: Option, /// What the commit did, one entry per record. pub ops: &'a [Op], } /// The entry one commit puts in the log. /// /// # Errors /// /// If the body will not encode, or `blocks` is missing the commit. pub fn records_written(written: &Commit<'_>, blocks: &BlockMap) -> Result { Ok(Body { event: Event::Append, bytes: encode(&Append { repo: written.did, commit: written.commit, rev: written.rev, since: written.since, blocks: car::write(written.commit, blocks)?, ops: written.ops, prev_data: written.prev_data, rebase: false, too_big: false, blobs: &[], })?, }) } /// The four entries a new account puts in the log. /// /// They are built together because they have to be written together: a /// consumer that reads the commit before the account exists has no way to ask /// for what it missed. /// /// # Errors /// /// If a body will not encode, or `blocks` is missing the commit. pub fn account_created( did: &Did, handle: &Handle, commit: Cid, rev: &Tid, blocks: &BlockMap, ) -> Result<[Body; 4], Error> { let mut root = BlockMap::new(); root.insert(commit, blocks.get(&commit)?.into_owned()); Ok([ Body { event: Event::Identity, bytes: encode(&Identity { did, handle })?, }, Body { event: Event::Account, bytes: encode(&Account { did, active: true })?, }, Body { event: Event::Append, bytes: encode(&Append { repo: did, commit, rev, since: None, blocks: car::write(commit, blocks)?, ops: &[], prev_data: None, rebase: false, too_big: false, blobs: &[], })?, }, Body { event: Event::Sync, bytes: encode(&Sync { did, rev, blocks: car::write(commit, &root)?, })?, }, ]) }