Skip to main content

xmtp_content_types/
intent.rs

1use 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}