xmtp_content_types/
lib.rs1pub mod actions;
2pub mod attachment;
3pub mod delete_message;
4pub mod encryption;
5pub mod group_updated;
6pub mod intent;
7pub mod leave_request;
8pub mod markdown;
9pub mod membership_change;
10pub mod multi_remote_attachment;
11pub mod reaction;
12pub mod read_receipt;
13pub mod remote_attachment;
14pub mod reply;
15pub mod text;
16pub mod transaction_reference;
17mod utils;
18pub mod wallet_send_calls;
19
20use prost::Message;
21use thiserror::Error;
22use xmtp_common::ErrorCode;
23use xmtp_proto::xmtp::mls::message_contents::{ContentTypeId, EncodedContent};
24
25#[cfg(test)]
26mod compatibility_test;
27#[cfg(any(test, feature = "test-utils"))]
28pub mod test_utils;
29
30#[derive(Debug, Error, ErrorCode)]
31#[error_code(internal)]
32pub enum CodecError {
33 #[error("encode error {0}")]
37 Encode(String),
38 #[error("decode error {0}")]
42 Decode(String),
43 #[error("codec not found for {0:?}")]
47 CodecNotFound(ContentTypeId),
48 #[error("invalid content type")]
52 InvalidContentType,
53}
54
55pub enum ContentType {
56 Text,
57 Markdown,
58 GroupMembershipChange,
59 GroupUpdated,
60 Reaction,
61 ReadReceipt,
62 Reply,
63 Actions,
64 Attachment,
65 Intent,
66 RemoteAttachment,
67 MultiRemoteAttachment,
68 TransactionReference,
69 WalletSendCalls,
70 DeviceSyncMessage,
71 LeaveRequest,
72 DeleteMessage,
73}
74
75impl TryFrom<&str> for ContentType {
76 type Error = String;
77
78 fn try_from(type_id: &str) -> Result<Self, Self::Error> {
79 match type_id {
80 text::TextCodec::TYPE_ID => Ok(Self::Text),
81 markdown::MarkdownCodec::TYPE_ID => Ok(Self::Markdown),
82 membership_change::GroupMembershipChangeCodec::TYPE_ID => {
83 Ok(Self::GroupMembershipChange)
84 }
85 group_updated::GroupUpdatedCodec::TYPE_ID => Ok(Self::GroupUpdated),
86 reaction::ReactionCodec::TYPE_ID => Ok(Self::Reaction),
87 read_receipt::ReadReceiptCodec::TYPE_ID => Ok(Self::ReadReceipt),
88 reply::ReplyCodec::TYPE_ID => Ok(Self::Reply),
89 attachment::AttachmentCodec::TYPE_ID => Ok(Self::Attachment),
90 remote_attachment::RemoteAttachmentCodec::TYPE_ID => Ok(Self::RemoteAttachment),
91 multi_remote_attachment::MultiRemoteAttachmentCodec::TYPE_ID => {
92 Ok(Self::MultiRemoteAttachment)
93 }
94 transaction_reference::TransactionReferenceCodec::TYPE_ID => {
95 Ok(Self::TransactionReference)
96 }
97 wallet_send_calls::WalletSendCallsCodec::TYPE_ID => Ok(Self::WalletSendCalls),
98 leave_request::LeaveRequestCodec::TYPE_ID => Ok(Self::LeaveRequest),
99 actions::ActionsCodec::TYPE_ID => Ok(Self::Actions),
100 intent::IntentCodec::TYPE_ID => Ok(Self::Intent),
101 delete_message::DeleteMessageCodec::TYPE_ID => Ok(Self::DeleteMessage),
102 _ => Err(format!("Unknown content type ID: {type_id}")),
103 }
104 }
105}
106
107pub trait ContentCodec<T> {
108 fn content_type() -> ContentTypeId;
109 fn encode(content: T) -> Result<EncodedContent, CodecError>;
110 fn decode(content: EncodedContent) -> Result<T, CodecError>;
111 fn should_push() -> bool;
112}
113
114pub fn encoded_content_to_bytes(content: EncodedContent) -> Vec<u8> {
115 let mut buf = Vec::new();
116 content.encode(&mut buf).unwrap();
117 buf
118}
119
120pub fn bytes_to_encoded_content(bytes: Vec<u8>) -> EncodedContent {
121 EncodedContent::decode(&mut bytes.as_slice()).unwrap()
122}
123
124#[cfg(test)]
125mod tests {
126 use std::collections::HashMap;
127
128 use super::*;
129
130 #[test]
131 fn test_encoded_content_conversion() {
132 let original = EncodedContent {
134 r#type: Some(ContentTypeId {
135 authority_id: "".to_string(),
136 type_id: "test".to_string(),
137 version_major: 0,
138 version_minor: 0,
139 }),
140 parameters: HashMap::new(),
141 compression: None,
142 content: vec![1, 2, 3, 4],
143 fallback: Some("test".to_string()),
144 };
145
146 let bytes = encoded_content_to_bytes(original.clone());
148
149 let recovered = bytes_to_encoded_content(bytes);
151
152 assert_eq!(recovered.content, original.content);
154 assert_eq!(recovered.fallback, original.fallback);
155 }
156}