Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
8.0 kB · 241 lines
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242use knot_types::{ AccountDid, AtUri, RecordAddress, RecordBody, RecordBodyError, RecordCid, RepoDid, UnixSeconds,};use serde::{Deserialize, Serialize, Serializer};
use crate::issue::MarkdownBody;use crate::{CreatedAt, RecordError, encoded};
pub const COMMENT_COLLECTION: &str = "sh.tangled.feed.comment";
pub const MARKDOWN_COLLECTION: &str = "sh.tangled.markup.markdown";
pub fn comment_collection() -> knot_types::RecordCollection { knot_types::RecordCollection::new(COMMENT_COLLECTION).expect("Comment collection is valid nsid")}
pub fn markup_collection() -> knot_types::RecordCollection { knot_types::RecordCollection::new(MARKDOWN_COLLECTION).expect("Markup collection is valid nsid")}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]#[serde(transparent)]pub struct PullRoundIdx(u32);
impl PullRoundIdx { pub const fn new(index: u32) -> Self { Self(index) }}#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]pub struct CommentRef { address: RecordAddress, cid: RecordCid,}
impl CommentRef { pub fn new(address: RecordAddress, cid: RecordCid) -> Self { Self { address, cid } }
pub fn address(&self) -> &RecordAddress { &self.address }
pub fn cid(&self) -> RecordCid { self.cid }}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct CommentMarkup { text: MarkdownBody, original: Option<MarkdownBody>,}
impl CommentMarkup { pub fn new(text: MarkdownBody, original: Option<MarkdownBody>) -> Self { Self { text, original } }}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct CommentRecord { repo: RepoDid, subject: CommentRef, body: CommentMarkup, reply_to: Option<CommentRef>, pull_round_idx: Option<PullRoundIdx>, created_at: CreatedAt, editor: AccountDid,}
impl CommentRecord { #[allow(clippy::too_many_arguments)] pub fn new( repo: RepoDid, subject: CommentRef, body: CommentMarkup, reply_to: Option<CommentRef>, pull_round_idx: Option<PullRoundIdx>, created_at: UnixSeconds, editor: AccountDid, ) -> Result<Self, RecordError> { Ok(Self { repo, subject, body, reply_to, pull_round_idx, created_at: CreatedAt::at(created_at)?, editor, }) }
pub fn body(&self) -> Result<RecordBody, RecordBodyError> { encoded(self) }}
#[derive(Serialize)]struct CommentRefEncoding { uri: AtUri<String>, cid: RecordCid,}
fn ref_encoding(reference: &CommentRef, repo: &RepoDid) -> CommentRefEncoding { CommentRefEncoding { uri: reference.address.at_uri(repo), cid: reference.cid, }}
#[derive(Serialize)]struct MarkupEncoding<'a> { #[serde(rename = "$type")] markup_type: &'static str, text: &'a MarkdownBody, #[serde(skip_serializing_if = "Option::is_none")] original: &'a Option<MarkdownBody>,}
#[derive(Serialize)]struct CommentEncoding<'a> { #[serde(rename = "$type")] record_type: knot_types::RecordCollection, subject: CommentRefEncoding, body: MarkupEncoding<'a>, #[serde(rename = "createdAt")] created_at: &'a CreatedAt, #[serde(skip_serializing_if = "Option::is_none", rename = "replyTo")] reply_to: Option<CommentRefEncoding>, #[serde(skip_serializing_if = "Option::is_none", rename = "pullRoundIdx")] pull_round_idx: Option<PullRoundIdx>, #[serde(rename = "x-tngl-editor")] editor: &'a AccountDid,}
impl Serialize for CommentRecord { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { CommentEncoding { record_type: comment_collection(), subject: ref_encoding(&self.subject, &self.repo), body: MarkupEncoding { markup_type: MARKDOWN_COLLECTION, text: &self.body.text, original: &self.body.original, }, created_at: &self.created_at, reply_to: self .reply_to .as_ref() .map(|reply| ref_encoding(reply, &self.repo)), pull_round_idx: self.pull_round_idx, editor: &self.editor, } .serialize(serializer) }}
#[cfg(test)]mod tests { use super::*; use crate::frozen; use knot_types::RecordRkey;
const COMMENT: &str = "a564626f6479a26474657874646b656c70652474797065781a73682e74616e676c65642e6d61726b75702e6d61726b646f776e6524747970657773682e74616e676c65642e666565642e636f6d6d656e74677375626a656374a263636964d82a58250001711220a9491f4c1bf7b0cffbadcba2db8f028e4b3f2867cb59e1f3a0bc1968f3c5124263757269783661743a2f2f6469643a706c633a73717569642f73682e74616e676c65642e7265706f2e69737375652f336c75627270747835376432326963726561746564417474313937302d30312d30315430303a30303a30315a6d782d746e676c2d656469746f7278206469643a706c633a3366776563646e76746373636a6e72783270346e37616c7a";
const ANSWERED: &str = "a664626f6479a36474657874646b656c70652474797065781a73682e74616e676c65642e6d61726b75702e6d61726b646f776e686f726967696e616c6c6b656c702c206265666f72656524747970657773682e74616e676c65642e666565642e636f6d6d656e74677265706c79546fa263636964d82a582500017112205782b18687e6cf8a482fc32d2db5b196d8821c458a0c069c6acf3953446e7bb563757269783861743a2f2f6469643a706c633a73717569642f73682e74616e676c65642e666565642e636f6d6d656e742f336c7562727074783537643434677375626a656374a263636964d82a58250001711220a9491f4c1bf7b0cffbadcba2db8f028e4b3f2867cb59e1f3a0bc1968f3c5124263757269783661743a2f2f6469643a706c633a73717569642f73682e74616e676c65642e7265706f2e69737375652f336c75627270747835376432326963726561746564417474313937302d30312d30315430303a30303a30315a6d782d746e676c2d656469746f7278206469643a706c633a3366776563646e76746373636a6e72783270346e37616c7a";
fn editor() -> AccountDid { AccountDid::new("did:plc:3fwecdnvtcscjnrx2p4n7alz").unwrap() }
fn squid() -> RepoDid { RepoDid::new("did:plc:squid").unwrap() }
fn subject() -> CommentRef { CommentRef::new( RecordAddress::new( crate::issue::issue_collection(), RecordRkey::new("3lubrptx57d22").unwrap(), ), knot_types::RecordCid::new(knot_types::dag_cbor_cid(b"subject")).unwrap(), ) }
fn reply() -> CommentRef { CommentRef::new( RecordAddress::new( comment_collection(), RecordRkey::new("3lubrptx57d44").unwrap(), ), knot_types::RecordCid::new(knot_types::dag_cbor_cid(b"reply")).unwrap(), ) }
#[test] fn comment_record_bytes_frozen() { let record = CommentRecord::new( squid(), subject(), CommentMarkup::new(MarkdownBody::new("kelp").unwrap(), None), None, None, UnixSeconds::new(1), editor(), ) .unwrap(); let body = record.body().unwrap(); assert_eq!( body.as_bytes(), frozen(COMMENT).as_slice(), "furst comment ever written must produce exactly these bytes: keys ordered by \ length, empty fields left out, whole seconds, and utc z" ); }
#[test] fn comment_with_reply_and_original_freezes_its_own_bytes() { let record = CommentRecord::new( squid(), subject(), CommentMarkup::new( MarkdownBody::new("kelp").unwrap(), Some(MarkdownBody::new("kelp, before").unwrap()), ), Some(reply()), None, UnixSeconds::new(1), editor(), ) .unwrap(); let body = record.body().unwrap(); assert_eq!( body.as_bytes(), frozen(ANSWERED).as_slice(), "reply and original should probably go under their own keys, byte order unchanged" ); }}