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