lxmf_core/message/
payload.rs1use serde::{Deserialize, Serialize};
2use serde_bytes::ByteBuf;
3
4use crate::error::LxmfError;
5use alloc::format;
6use alloc::string::ToString;
7use alloc::vec::Vec;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct Payload {
11 pub timestamp: f64,
12 pub content: Option<ByteBuf>,
13 pub title: Option<ByteBuf>,
14 pub fields: Option<rmpv::Value>,
15 pub stamp: Option<ByteBuf>,
16}
17
18impl Payload {
19 pub fn new(
20 timestamp: f64,
21 content: Option<Vec<u8>>,
22 title: Option<Vec<u8>>,
23 fields: Option<rmpv::Value>,
24 stamp: Option<Vec<u8>>,
25 ) -> Self {
26 Self {
27 timestamp,
28 content: content.map(ByteBuf::from),
29 title: title.map(ByteBuf::from),
30 fields,
31 stamp: stamp.map(ByteBuf::from),
32 }
33 }
34
35 pub fn to_msgpack(&self) -> Result<Vec<u8>, LxmfError> {
36 if let Some(stamp) = &self.stamp {
37 let list = (
38 self.timestamp,
39 self.title.clone(),
40 self.content.clone(),
41 self.fields.clone(),
42 stamp.clone(),
43 );
44 rmp_serde::to_vec(&list).map_err(|e| LxmfError::Encode(e.to_string()))
45 } else {
46 self.to_msgpack_without_stamp()
47 }
48 }
49
50 pub fn to_msgpack_without_stamp(&self) -> Result<Vec<u8>, LxmfError> {
51 let list = (self.timestamp, self.title.clone(), self.content.clone(), self.fields.clone());
52 rmp_serde::to_vec(&list).map_err(|e| LxmfError::Encode(e.to_string()))
53 }
54
55 pub fn from_msgpack(bytes: &[u8]) -> Result<Self, LxmfError> {
56 let value = rmp_serde::from_slice::<rmpv::Value>(bytes)
57 .map_err(|e| LxmfError::Decode(e.to_string()))?;
58 let rmpv::Value::Array(items) = value else {
59 return Err(LxmfError::Decode("invalid payload structure".into()));
60 };
61 if items.len() < 4 || items.len() > 5 {
62 return Err(LxmfError::Decode("invalid payload length".into()));
63 }
64 let timestamp = items
65 .first()
66 .and_then(|value| value.as_f64())
67 .ok_or_else(|| LxmfError::Decode("invalid payload timestamp".into()))?;
68 let title = value_to_bytes(items.get(1), "title")?.map(ByteBuf::from);
69 let content = value_to_bytes(items.get(2), "content")?.map(ByteBuf::from);
70 let fields = match items.get(3) {
71 Some(rmpv::Value::Nil) | None => None,
72 Some(value) => Some(value.clone()),
73 };
74 let stamp = if items.len() == 5 {
75 value_to_bytes(items.get(4), "stamp")?.map(ByteBuf::from)
76 } else {
77 None
78 };
79 Ok(Self { timestamp, content, title, fields, stamp })
80 }
81}
82
83fn value_to_bytes(value: Option<&rmpv::Value>, field: &str) -> Result<Option<Vec<u8>>, LxmfError> {
84 match value {
85 Some(rmpv::Value::Binary(bin)) => Ok(Some(bin.clone())),
86 Some(rmpv::Value::String(text)) => text
87 .as_str()
88 .map(|s| Some(s.as_bytes().to_vec()))
89 .ok_or_else(|| LxmfError::Decode(format!("invalid payload {field} string"))),
90 Some(rmpv::Value::Nil) | None => Ok(None),
91 _ => Err(LxmfError::Decode(format!("invalid payload {field}"))),
92 }
93}