Skip to main content

jmap_chat_client/methods/
custom_emoji.rs

1//! CustomEmoji method implementations for SessionClient.
2//!
3//! Spec: JMAP Chat draft §4.16 (CustomEmoji/get, /changes, /set, /query, /queryChanges)
4//! Capability: urn:ietf:params:jmap:chat
5
6use jmap_types::{Id, State};
7
8use super::{
9    ChangesResponse, CustomEmojiCreateInput, CustomEmojiQueryInput, GetResponse,
10    QueryChangesResponse, QueryResponse, SetResponse,
11};
12
13impl super::SessionClient {
14    /// Fetch CustomEmoji objects by IDs (JMAP Chat §4.16 CustomEmoji/get).
15    ///
16    /// If `ids` is `None`, returns all CustomEmoji objects visible to the account
17    /// (Space-specific and server-global).
18    pub async fn custom_emoji_get(
19        &self,
20        ids: Option<&[Id]>,
21        properties: Option<&[&str]>,
22    ) -> Result<GetResponse<jmap_chat_types::CustomEmoji>, jmap_base_client::ClientError> {
23        let (api_url, account_id) = self.session_parts()?;
24        // Omit `ids` / `properties` when None — see the matching comment on
25        // `chat_get` for the rationale (consistent with set/changes/query).
26        let mut args = serde_json::json!({ "accountId": account_id });
27        if let Some(id_slice) = ids {
28            args["ids"] = serde_json::to_value(id_slice).expect("Id slice Serialize is infallible");
29        }
30        if let Some(props) = properties {
31            args["properties"] = serde_json::Value::Array(
32                props.iter().copied().map(serde_json::Value::from).collect(),
33            );
34        }
35        let req = super::build_request("CustomEmoji/get", args, super::USING_CHAT);
36        let resp = self.call_internal(api_url, &req).await?;
37        jmap_base_client::extract_response(&resp, super::CALL_ID)
38    }
39
40    /// Fetch changes to CustomEmoji objects since `since_state`
41    /// (RFC 8620 §5.2 / JMAP Chat §4.16 CustomEmoji/changes).
42    pub async fn custom_emoji_changes(
43        &self,
44        since_state: &State,
45        max_changes: Option<u64>,
46    ) -> Result<ChangesResponse, jmap_base_client::ClientError> {
47        // Defence-in-depth: see `chat_changes`.
48        if since_state.as_ref().is_empty() {
49            return Err(jmap_base_client::ClientError::InvalidArgument(
50                "custom_emoji_changes: since_state may not be empty".into(),
51            ));
52        }
53        let (api_url, account_id) = self.session_parts()?;
54        let mut args = serde_json::json!({
55            "accountId": account_id,
56            "sinceState": since_state,
57        });
58        if let Some(mc) = max_changes {
59            args["maxChanges"] = mc.into();
60        }
61        let req = super::build_request("CustomEmoji/changes", args, super::USING_CHAT);
62        let resp = self.call_internal(api_url, &req).await?;
63        jmap_base_client::extract_response(&resp, super::CALL_ID)
64    }
65
66    /// Create a CustomEmoji (RFC 8620 §5.3 / JMAP Chat §4.16 CustomEmoji/set create).
67    ///
68    /// When `input.client_id` is `None`, a ULID is generated automatically.
69    pub async fn custom_emoji_create(
70        &self,
71        input: &CustomEmojiCreateInput<'_>,
72    ) -> Result<SetResponse, jmap_base_client::ClientError> {
73        if input.name.is_empty() {
74            return Err(jmap_base_client::ClientError::InvalidArgument(
75                "custom_emoji_create: name may not be empty".into(),
76            ));
77        }
78        let (api_url, account_id) = self.session_parts()?;
79        let mut create_obj = serde_json::json!({
80            "name": input.name,
81            "blobId": input.blob_id,
82        });
83        if let Some(sid) = input.space_id {
84            create_obj["spaceId"] = sid.as_ref().into();
85        }
86        let client_id = super::resolve_client_id(input.client_id);
87        let args = serde_json::json!({
88            "accountId": account_id,
89            "create": { client_id: create_obj },
90        });
91        let req = super::build_request("CustomEmoji/set", args, super::USING_CHAT);
92        let resp = self.call_internal(api_url, &req).await?;
93        jmap_base_client::extract_response(&resp, super::CALL_ID)
94    }
95
96    /// Destroy CustomEmoji objects (RFC 8620 §5.3 / JMAP Chat §4.16 CustomEmoji/set destroy).
97    ///
98    /// `ids` must be non-empty; the guard fires before any network call.
99    pub async fn custom_emoji_destroy(
100        &self,
101        ids: &[Id],
102    ) -> Result<SetResponse, jmap_base_client::ClientError> {
103        if ids.is_empty() {
104            return Err(jmap_base_client::ClientError::InvalidArgument(
105                "custom_emoji_destroy: ids may not be empty".into(),
106            ));
107        }
108        let (api_url, account_id) = self.session_parts()?;
109        let args = serde_json::json!({
110            "accountId": account_id,
111            "destroy": ids,
112        });
113        let req = super::build_request("CustomEmoji/set", args, super::USING_CHAT);
114        let resp = self.call_internal(api_url, &req).await?;
115        jmap_base_client::extract_response(&resp, super::CALL_ID)
116    }
117
118    /// Query CustomEmoji IDs (RFC 8620 §5.5 / JMAP Chat §4.16 CustomEmoji/query).
119    pub async fn custom_emoji_query(
120        &self,
121        input: &CustomEmojiQueryInput<'_>,
122    ) -> Result<QueryResponse, jmap_base_client::ClientError> {
123        let (api_url, account_id) = self.session_parts()?;
124        let mut args = serde_json::json!({
125            "accountId": account_id,
126        });
127        if let Some(sid) = input.filter_space_id {
128            args["filter"] = serde_json::json!({ "spaceId": sid });
129        }
130        if let Some(p) = input.position {
131            args["position"] = p.into();
132        }
133        if let Some(l) = input.limit {
134            args["limit"] = l.into();
135        }
136        let req = super::build_request("CustomEmoji/query", args, super::USING_CHAT);
137        let resp = self.call_internal(api_url, &req).await?;
138        jmap_base_client::extract_response(&resp, super::CALL_ID)
139    }
140
141    /// Fetch query-result changes for CustomEmoji since `since_query_state`
142    /// (RFC 8620 §5.6 / JMAP Chat §4.16 CustomEmoji/queryChanges).
143    pub async fn custom_emoji_query_changes(
144        &self,
145        since_query_state: &State,
146        max_changes: Option<u64>,
147    ) -> Result<QueryChangesResponse, jmap_base_client::ClientError> {
148        // Defence-in-depth: see `chat_changes`.
149        if since_query_state.as_ref().is_empty() {
150            return Err(jmap_base_client::ClientError::InvalidArgument(
151                "custom_emoji_query_changes: since_query_state may not be empty".into(),
152            ));
153        }
154        let (api_url, account_id) = self.session_parts()?;
155        let mut args = serde_json::json!({
156            "accountId": account_id,
157            "sinceQueryState": since_query_state,
158        });
159        if let Some(mc) = max_changes {
160            args["maxChanges"] = mc.into();
161        }
162        let req = super::build_request("CustomEmoji/queryChanges", args, super::USING_CHAT);
163        let resp = self.call_internal(api_url, &req).await?;
164        jmap_base_client::extract_response(&resp, super::CALL_ID)
165    }
166}