nederlandskie/src/frames.rs

185 lines
6.1 KiB
Rust

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<typeof messageFrameHeader>
// export const errorFrameHeader = z.object({
// op: z.literal(FrameType.Error),
// })
// export type ErrorFrameHeader = z.infer<typeof errorFrameHeader>
// ```
#[derive(Debug, Clone, PartialEq, Eq)]
enum FrameHeader {
Message(Option<String>),
Error,
}
impl TryFrom<Ipld> for FrameHeader {
type Error = anyhow::Error;
fn try_from(value: Ipld) -> Result<Self, <FrameHeader as TryFrom<Ipld>>::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<MessageFrame>),
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<Self, <Frame as TryFrom<&[u8]>>::Error> {
let mut cursor = Cursor::new(value);
let (left, right) = match serde_ipld_dagcbor::from_reader::<Ipld, _>(&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::<Ipld>(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::<Commit>(
right,
)?)),
}))),
Some("#handle") => Ok(Frame::Message(Box::new(MessageFrame {
body: Message::Handle(Box::new(serde_ipld_dagcbor::from_slice::<Handle>(
right,
)?)),
}))),
Some("#info") => Ok(Frame::Message(Box::new(MessageFrame {
body: Message::Info(Box::new(serde_ipld_dagcbor::from_slice::<Info>(right)?)),
}))),
Some("#migrate") => Ok(Frame::Message(Box::new(MessageFrame {
body: Message::Migrate(Box::new(serde_ipld_dagcbor::from_slice::<Migrate>(
right,
)?)),
}))),
Some("#tombstone") => Ok(Frame::Message(Box::new(MessageFrame {
body: Message::Tombstone(Box::new(
serde_ipld_dagcbor::from_slice::<Tombstone>(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<u8> {
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::<Ipld>(&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::<Ipld>(&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::<Ipld>(&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::<Ipld>(&data).expect("failed to deserialize");
let result = FrameHeader::try_from(ipld);
assert_eq!(
result.expect_err("must be failed").to_string(),
"invalid frame type"
);
}
}
}