jmap_chat_client/methods/
message.rs1use jmap_types::{Id, PatchObject, State};
15
16use super::{
17 ChangesResponse, GetResponse, MessageCreateInput, MessagePatch, MessageQueryInput,
18 QueryChangesResponse, QueryResponse, ReactionChange, SetResponse,
19};
20
21impl super::SessionClient {
22 pub async fn message_get(
27 &self,
28 ids: &[Id],
29 properties: Option<&[&str]>,
30 ) -> Result<GetResponse<jmap_chat_types::Message>, jmap_base_client::ClientError> {
31 if ids.is_empty() {
32 return Err(jmap_base_client::ClientError::InvalidArgument(
33 "message_get: ids may not be empty".into(),
34 ));
35 }
36 let (api_url, account_id) = self.session_parts()?;
37 let mut args = serde_json::json!({
41 "accountId": account_id,
42 "ids": ids,
43 });
44 if let Some(props) = properties {
45 args["properties"] = serde_json::Value::Array(
46 props.iter().copied().map(serde_json::Value::from).collect(),
47 );
48 }
49 let req = super::build_request("Message/get", args, super::USING_CHAT);
50 let resp = self.call_internal(api_url, &req).await?;
51 jmap_base_client::extract_response(&resp, super::CALL_ID)
52 }
53
54 pub async fn message_query(
64 &self,
65 input: &MessageQueryInput<'_>,
66 ) -> Result<QueryResponse, jmap_base_client::ClientError> {
67 if input.chat_id.is_none() && input.has_mention != Some(true) {
68 return Err(jmap_base_client::ClientError::InvalidArgument(
69 "message_query: chat_id or has_mention=true must be provided".into(),
70 ));
71 }
72 let (api_url, account_id) = self.session_parts()?;
73 let mut filter = serde_json::Map::new();
74 if let Some(id) = input.chat_id {
75 filter.insert("chatId".into(), id.as_ref().into());
76 }
77 if let Some(m) = input.has_mention {
78 filter.insert("hasMention".into(), m.into());
79 }
80 if let Some(a) = input.has_attachment {
81 filter.insert("hasAttachment".into(), a.into());
82 }
83 if let Some(t) = input.text {
84 filter.insert("text".into(), t.into());
85 }
86 if let Some(tid) = input.thread_root_id {
87 filter.insert("threadRootId".into(), tid.as_ref().into());
88 }
89 if let Some(a) = input.after {
90 filter.insert("after".into(), a.as_ref().into());
91 }
92 if let Some(b) = input.before {
93 filter.insert("before".into(), b.as_ref().into());
94 }
95 let filter_val = if filter.is_empty() {
96 serde_json::Value::Null
97 } else {
98 serde_json::Value::Object(filter)
99 };
100 let mut args = serde_json::json!({
101 "accountId": account_id,
102 "filter": filter_val,
103 "sort": [{"property": "sentAt", "isAscending": input.sort_ascending}],
104 });
105 if let Some(p) = input.position {
106 args["position"] = p.into();
107 }
108 if let Some(l) = input.limit {
109 args["limit"] = l.into();
110 }
111 let req = super::build_request("Message/query", args, super::USING_CHAT);
112 let resp = self.call_internal(api_url, &req).await?;
113 jmap_base_client::extract_response(&resp, super::CALL_ID)
114 }
115
116 pub async fn message_changes(
118 &self,
119 since_state: &State,
120 max_changes: Option<u64>,
121 ) -> Result<ChangesResponse, jmap_base_client::ClientError> {
122 if since_state.as_ref().is_empty() {
124 return Err(jmap_base_client::ClientError::InvalidArgument(
125 "message_changes: since_state may not be empty".into(),
126 ));
127 }
128 let (api_url, account_id) = self.session_parts()?;
129 let mut args = serde_json::json!({
130 "accountId": account_id,
131 "sinceState": since_state,
132 });
133 if let Some(mc) = max_changes {
134 args["maxChanges"] = mc.into();
135 }
136 let req = super::build_request("Message/changes", 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 pub async fn message_create(
164 &self,
165 input: &MessageCreateInput<'_>,
166 ) -> Result<SetResponse, jmap_base_client::ClientError> {
167 let (api_url, account_id) = self.session_parts()?;
168 let client_id = super::resolve_client_id(input.client_id);
169 let client_id_str: &str = &client_id;
172 let mut create_obj = serde_json::json!({
173 "chatId": input.chat_id,
174 "body": input.body,
175 "bodyType": serde_json::to_value(&input.body_type)
176 .map_err(jmap_base_client::ClientError::Parse)?,
177 "sentAt": input.sent_at.as_ref(),
178 });
179 if let Some(rt) = input.reply_to {
180 create_obj["replyTo"] = rt.as_ref().into();
181 }
182 let args = serde_json::json!({
183 "accountId": account_id,
184 "create": { client_id_str: create_obj },
185 });
186 let req = super::build_request("Message/set", args, super::USING_CHAT);
187 let resp = self.call_internal(api_url, &req).await?;
188 let set_resp: SetResponse = jmap_base_client::extract_response(&resp, super::CALL_ID)?;
189 if let Some(not_created) = &set_resp.not_created {
191 if let Some(err) = not_created.get(client_id_str) {
192 if err.error_type == "rateLimited" {
193 let retry_after = super::server_retry_after(err).ok_or_else(|| {
194 jmap_base_client::ClientError::UnexpectedResponse(
195 "rateLimited SetError missing serverRetryAfter".into(),
196 )
197 })?;
198 return Err(jmap_base_client::ClientError::RateLimited { retry_after });
199 }
200 }
201 }
202 Ok(set_resp)
203 }
204
205 pub async fn message_update(
216 &self,
217 id: &Id,
218 patch: &MessagePatch<'_>,
219 ) -> Result<SetResponse, jmap_base_client::ClientError> {
220 let (api_url, account_id) = self.session_parts()?;
221 let mut patch_map = serde_json::Map::new();
222 if let Some(b) = patch.body {
223 patch_map.insert("body".into(), b.into());
224 }
225 if let Some(bt) = &patch.body_type {
226 patch_map.insert(
227 "bodyType".into(),
228 serde_json::to_value(bt).map_err(jmap_base_client::ClientError::Parse)?,
229 );
230 }
231 if let Some(ra) = patch.read_at {
232 patch_map.insert("readAt".into(), ra.as_ref().into());
233 }
234 if let Some(rd) = &patch.read_disposition {
235 patch_map.insert(
236 "readDisposition".into(),
237 serde_json::to_value(rd).map_err(jmap_base_client::ClientError::Parse)?,
238 );
239 }
240 if let Some(da) = patch.deleted_at {
241 patch_map.insert("deletedAt".into(), da.as_ref().into());
242 }
243 if let Some(dfa) = patch.deleted_for_all {
244 patch_map.insert("deletedForAll".into(), dfa.into());
245 }
246 for change in patch.reaction_changes.unwrap_or(&[]) {
247 match change {
248 ReactionChange::Add {
249 sender_reaction_id,
250 emoji,
251 sent_at,
252 } => {
253 if sender_reaction_id.is_empty() {
254 return Err(jmap_base_client::ClientError::InvalidArgument(
255 "message_update: sender_reaction_id may not be empty".into(),
256 ));
257 }
258 if sender_reaction_id.contains('/') || sender_reaction_id.contains('~') {
259 return Err(jmap_base_client::ClientError::InvalidArgument(
260 "message_update: sender_reaction_id must not contain '/' or '~' \
261 (RFC 6901 JSON Pointer special characters)"
262 .into(),
263 ));
264 }
265 patch_map.insert(
266 format!("reactions/{sender_reaction_id}"),
267 serde_json::json!({"emoji": emoji, "sentAt": sent_at.as_ref()}),
268 );
269 }
270 ReactionChange::Remove { sender_reaction_id } => {
271 if sender_reaction_id.is_empty() {
272 return Err(jmap_base_client::ClientError::InvalidArgument(
273 "message_update: sender_reaction_id may not be empty".into(),
274 ));
275 }
276 if sender_reaction_id.contains('/') || sender_reaction_id.contains('~') {
277 return Err(jmap_base_client::ClientError::InvalidArgument(
278 "message_update: sender_reaction_id must not contain '/' or '~' \
279 (RFC 6901 JSON Pointer special characters)"
280 .into(),
281 ));
282 }
283 patch_map.insert(
284 format!("reactions/{sender_reaction_id}"),
285 serde_json::Value::Null,
286 );
287 }
288 }
289 }
290 let patch_value = serde_json::Value::Object(PatchObject::from_map(patch_map).into_inner());
294 let args = serde_json::json!({
295 "accountId": account_id,
296 "update": { id.as_ref(): patch_value },
297 });
298 let req = super::build_request("Message/set", args, super::USING_CHAT);
299 let resp = self.call_internal(api_url, &req).await?;
300 jmap_base_client::extract_response(&resp, super::CALL_ID)
301 }
302
303 pub async fn message_destroy(
308 &self,
309 ids: &[Id],
310 ) -> Result<SetResponse, jmap_base_client::ClientError> {
311 if ids.is_empty() {
312 return Err(jmap_base_client::ClientError::InvalidArgument(
313 "message_destroy: ids may not be empty".into(),
314 ));
315 }
316 let (api_url, account_id) = self.session_parts()?;
317 let args = serde_json::json!({
318 "accountId": account_id,
319 "destroy": ids,
320 });
321 let req = super::build_request("Message/set", args, super::USING_CHAT);
322 let resp = self.call_internal(api_url, &req).await?;
323 jmap_base_client::extract_response(&resp, super::CALL_ID)
324 }
325
326 pub async fn message_query_changes(
332 &self,
333 since_query_state: &State,
334 max_changes: Option<u64>,
335 ) -> Result<QueryChangesResponse, jmap_base_client::ClientError> {
336 if since_query_state.as_ref().is_empty() {
338 return Err(jmap_base_client::ClientError::InvalidArgument(
339 "message_query_changes: since_query_state may not be empty".into(),
340 ));
341 }
342 let (api_url, account_id) = self.session_parts()?;
343 let mut args = serde_json::json!({
344 "accountId": account_id,
345 "sinceQueryState": since_query_state,
346 });
347 if let Some(mc) = max_changes {
348 args["maxChanges"] = mc.into();
349 }
350 let req = super::build_request("Message/queryChanges", args, super::USING_CHAT);
351 let resp = self.call_internal(api_url, &req).await?;
352 jmap_base_client::extract_response(&resp, super::CALL_ID)
353 }
354}