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> {
35 if let Some(metadata) = &intent.metadata {
36 let intent_json = serde_json::to_vec(metadata).map_err(|e| {
37 CodecError::Encode(format!("Unable to serialize intent metadata. {e:?}"))
38 })?;
39 if intent_json.len() > INTENT_METADATA_LIMIT {
40 return Err(CodecError::Encode(format!(
41 "Intent metadata is too large. (limit: {}kb)",
42 INTENT_METADATA_LIMIT / 1024
43 )));
44 }
45 }
46
47 let intent_json = serde_json::to_vec(&intent)
48 .map_err(|e| CodecError::Encode(format!("Unable to serialize intent. {e:?}")))?;
49
50 Ok(EncodedContent {
51 r#type: Some(Self::content_type()),
52 content: intent_json,
53 fallback: Self::fallback(&intent),
54 ..Default::default()
55 })
56 }
57
58 fn decode(intent: EncodedContent) -> Result<Intent, CodecError> {
59 let intent = serde_json::from_slice(&intent.content)
60 .map_err(|e| CodecError::Decode(format!("Unable to deserialize intent. {e:?}")))?;
61
62 Ok(intent)
63 }
64
65 fn should_push() -> bool {
66 true
67 }
68}
69
70#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
71pub struct Intent {
72 pub id: String,
73 #[serde(rename = "actionId", alias = "action_id")]
74 pub action_id: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub metadata: Option<HashMap<String, Value>>,
77}
78
79#[cfg(test)]
80mod tests {
81 use super::{Intent, IntentCodec};
82 use crate::ContentCodec;
83 use serde_json::Value;
84
85 #[xmtp_common::test(unwrap_try = true)]
86 fn encode_decode_intent() {
88 let intent = Intent {
89 id: "thanksgiving_selection".to_string(),
90 action_id: "the_turkey_of_course".to_string(),
91 metadata: Some(
92 [
93 (
94 "amount_for_yourself".to_string(),
95 Value::String("7lbs".to_string()),
96 ),
97 ("hugs_from_grandma".to_string(), Value::Bool(true)),
98 ]
99 .into_iter()
100 .collect(),
101 ),
102 };
103
104 let encoded = IntentCodec::encode(intent.clone())?;
105 assert_eq!(
106 encoded.fallback(),
107 "User selected action: the_turkey_of_course"
108 );
109 let decoded = IntentCodec::decode(encoded)?;
110
111 assert_eq!(decoded, intent);
112 }
113}