use atrium_api::com::atproto::sync::subscribe_repos::{ Commit, Handle, Info, Message, Migrate, Tombstone, }; use libipld_core::ipld::Ipld; use std::io::Cursor; // original definition: //``` // export enum FrameType { // Message = 1, // Error = -1, // } // export const messageFrameHeader = z.object({ // op: z.literal(FrameType.Message), // Frame op // t: z.string().optional(), // Message body type discriminator // }) // export type MessageFrameHeader = z.infer // export const errorFrameHeader = z.object({ // op: z.literal(FrameType.Error), // }) // export type ErrorFrameHeader = z.infer // ``` #[derive(Debug, Clone, PartialEq, Eq)] enum FrameHeader { Message(Option), Error, } impl TryFrom for FrameHeader { type Error = anyhow::Error; fn try_from(value: Ipld) -> Result>::Error> { if let Ipld::Map(map) = value { if let Some(Ipld::Integer(i)) = map.get("op") { match i { 1 => { let t = if let Some(Ipld::String(s)) = map.get("t") { Some(s.clone()) } else { None }; return Ok(FrameHeader::Message(t)); } -1 => return Ok(FrameHeader::Error), _ => {} } } } Err(anyhow::anyhow!("invalid frame type")) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Frame { Message(Box), Error(ErrorFrame), } #[derive(Debug, Clone, PartialEq, Eq)] pub struct MessageFrame { pub body: Message, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ErrorFrame { // TODO // body: Value, } impl TryFrom<&[u8]> for Frame { type Error = anyhow::Error; fn try_from(value: &[u8]) -> Result>::Error> { let mut cursor = Cursor::new(value); let (left, right) = match serde_ipld_dagcbor::from_reader::(&mut cursor) { Err(serde_ipld_dagcbor::DecodeError::TrailingData) => { value.split_at(cursor.position() as usize) } _ => { // TODO return Err(anyhow::anyhow!("invalid frame type")); } }; let ipld = serde_ipld_dagcbor::from_slice::(left)?; let header = FrameHeader::try_from(ipld)?; match header { FrameHeader::Message(t) => match t.as_deref() { Some("#commit") => Ok(Frame::Message(Box::new(MessageFrame { body: Message::Commit(Box::new(serde_ipld_dagcbor::from_slice::( right, )?)), }))), Some("#handle") => Ok(Frame::Message(Box::new(MessageFrame { body: Message::Handle(Box::new(serde_ipld_dagcbor::from_slice::( right, )?)), }))), Some("#info") => Ok(Frame::Message(Box::new(MessageFrame { body: Message::Info(Box::new(serde_ipld_dagcbor::from_slice::(right)?)), }))), Some("#migrate") => Ok(Frame::Message(Box::new(MessageFrame { body: Message::Migrate(Box::new(serde_ipld_dagcbor::from_slice::( right, )?)), }))), Some("#tombstone") => Ok(Frame::Message(Box::new(MessageFrame { body: Message::Tombstone(Box::new( serde_ipld_dagcbor::from_slice::(right)?, )), }))), _ => { let tag = t.as_deref(); Err(anyhow::anyhow!("frame not implemented: tag={tag:?}")) } }, FrameHeader::Error => Ok(Frame::Error(ErrorFrame {})), } } } #[cfg(test)] mod tests { use super::*; fn serialized_data(s: &str) -> Vec { assert!(s.len() % 2 == 0); let b2u = |b: u8| match b { b'0'..=b'9' => b - b'0', b'a'..=b'f' => b - b'a' + 10, _ => unreachable!(), }; s.as_bytes() .chunks(2) .map(|b| (b2u(b[0]) << 4) + b2u(b[1])) .collect() } #[test] fn deserialize_message_frame_header() { // {"op": 1, "t": "#commit"} let data = serialized_data("a2626f700161746723636f6d6d6974"); let ipld = serde_ipld_dagcbor::from_slice::(&data).expect("failed to deserialize"); let result = FrameHeader::try_from(ipld); assert_eq!( result.expect("failed to deserialize"), FrameHeader::Message(Some(String::from("#commit"))) ); } #[test] fn deserialize_error_frame_header() { // {"op": -1} let data = serialized_data("a1626f7020"); let ipld = serde_ipld_dagcbor::from_slice::(&data).expect("failed to deserialize"); let result = FrameHeader::try_from(ipld); assert_eq!(result.expect("failed to deserialize"), FrameHeader::Error); } #[test] fn deserialize_invalid_frame_header() { { // {"op": 2, "t": "#commit"} let data = serialized_data("a2626f700261746723636f6d6d6974"); let ipld = serde_ipld_dagcbor::from_slice::(&data).expect("failed to deserialize"); let result = FrameHeader::try_from(ipld); assert_eq!( result.expect_err("must be failed").to_string(), "invalid frame type" ); } { // {"op": -2} let data = serialized_data("a1626f7021"); let ipld = serde_ipld_dagcbor::from_slice::(&data).expect("failed to deserialize"); let result = FrameHeader::try_from(ipld); assert_eq!( result.expect_err("must be failed").to_string(), "invalid frame type" ); } } }