xmtp_content_types/
intent.rs1use crate::{CodecError, ContentCodec};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5use xmtp_proto::xmtp::mls::message_contents::{ContentTypeId, EncodedContent};
6
7const INTENT_METADATA_LIMIT: usize = 10 * 1024;
8
9pub struct IntentCodec;
10impl IntentCodec {
11 const AUTHORITY_ID: &str = "coinbase.com";
12 pub const TYPE_ID: &str = "intent";
13 pub const MAJOR_VERSION: u32 = 1;
14 pub const MINOR_VERSION: u32 = 0;
15}
16
17impl IntentCodec {
18 fn fallback(intent: &Intent) -> Option<String> {
19 Some(format!("User selected action: {}", intent.action_id))
20 }
21}
22
23impl ContentCodec<Intent> for IntentCodec {
24 fn content_type() -> ContentTypeId {
25 ContentTypeId {
26 authority_id: Self::AUTHORITY_ID.to_string(),
27 type_id: Self::TYPE_ID.to_string(),
28 version_major: Self::MAJOR_VERSION,
29 version_minor: Self::MINOR_VERSION,
30 }
31 }
32
33 fn encode(intent: Intent) -> Result<EncodedContent, CodecError> {
34 if let Some(metadata) = &intent.metadata {
35 let intent_json = serde_json::to_vec(metadata).map_err(|e| {
36 CodecError::Encode(format!("Unable to serialize intent metadata. {e:?}"))
37 })?;
38 if intent_json.len() > INTENT_METADATA_LIMIT {
39 return Err(CodecError::Encode(format!(
40 "Intent metadata is too large. (limit: {}kb)",
41 INTENT_METADATA_LIMIT / 1024
42 )));
43 }
44 }
45
46 let intent_json = serde_json::to_vec(&intent)
47 .map_err(|e| CodecError::Encode(format!("Unable to serialize intent. {e:?}")))?;
48
49 Ok(EncodedContent {
50 r#type: Some(Self::content_type()),
51 content: intent_json,
52 fallback: Self::fallback(&intent),
53 ..Default::default()
54 })
55 }
56
57 fn decode(intent: EncodedContent) -> Result<Intent, CodecError> {
58 let intent = serde_json::from_slice(&intent.content)
59 .map_err(|e| CodecError::Decode(format!("Unable to deserialize intent. {e:?}")))?;
60
61 Ok(intent)
62 }
63
64 fn should_push() -> bool {
65 true
66 }
67}
68
69#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
70pub struct Intent {
71 pub id: String,
72 #[serde(rename = "actionId", alias = "action_id")]
73 pub action_id: String,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub metadata: Option<HashMap<String, Value>>,
76}
77
78#[cfg(test)]
79mod tests {
80 use super::{Intent, IntentCodec};
81 use crate::ContentCodec;
82 use serde_json::Value;
83
84 #[xmtp_common::test(unwrap_try = true)]
85 fn encode_decode_intent() {
86 let intent = Intent {
87 id: "thanksgiving_selection".to_string(),
88 action_id: "the_turkey_of_course".to_string(),
89 metadata: Some(
90 [
91 (
92 "amount_for_yourself".to_string(),
93 Value::String("7lbs".to_string()),
94 ),
95 ("hugs_from_grandma".to_string(), Value::Bool(true)),
96 ]
97 .into_iter()
98 .collect(),
99 ),
100 };
101
102 let encoded = IntentCodec::encode(intent.clone())?;
103 assert_eq!(
104 encoded.fallback(),
105 "User selected action: the_turkey_of_course"
106 );
107 let decoded = IntentCodec::decode(encoded)?;
108
109 assert_eq!(decoded, intent);
110 }
111}