Skip to main content

io_jmap/rfc8621/mailbox/
set.rs

1//! JMAP `Mailbox/set` coroutine (RFC 8621 §2.6): wraps the generic [`JmapSet`]
2//! with [`JmapMailboxSetArgs`] (create/update/destroy) and decodes per-object
3//! [`JmapMailboxSetItemError`] payloads.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! use std::{
9//!     io::{Read, Write},
10//!     net::TcpStream,
11//! };
12//!
13//! use io_jmap::{
14//!     coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
15//!     rfc8620::JmapSession,
16//!     rfc8621::mailbox::set::{JmapMailboxSet, JmapMailboxSetArgs},
17//! };
18//! use secrecy::SecretString;
19//!
20//! // Ready stream needed (TCP-connected, TLS-negociated)
21//! let mut stream = TcpStream::connect("api.example.com:443").unwrap();
22//! let mut buf = [0u8; 4096];
23//!
24//! let session: JmapSession = serde_json::from_str(r#"{
25//!     "username": "",
26//!     "accounts": {},
27//!     "primaryAccounts": {"urn:ietf:params:jmap:mail": "a1"},
28//!     "capabilities": {},
29//!     "apiUrl": "https://api.example.com/jmap/",
30//!     "downloadUrl": "",
31//!     "uploadUrl": "",
32//!     "eventSourceUrl": "",
33//!     "state": ""
34//! }"#).unwrap();
35//! let auth = SecretString::from("Bearer xyz");
36//! let mut coroutine =
37//!     JmapMailboxSet::new(&session, &auth, JmapMailboxSetArgs::default()).unwrap();
38//! let mut arg = None;
39//!
40//! let out = loop {
41//!     match coroutine.resume(arg.take()) {
42//!         JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
43//!             stream.write_all(&bytes).unwrap();
44//!         }
45//!         JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
46//!             let n = stream.read(&mut buf).unwrap();
47//!             arg = Some(&buf[..n]);
48//!         }
49//!         JmapCoroutineState::Complete(Ok(out)) => break out,
50//!         JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
51//!     }
52//! };
53//!
54//! println!("new state {}", out.new_state);
55//! ```
56
57use core::fmt;
58
59use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
60
61use log::trace;
62use secrecy::SecretString;
63use serde::Serialize;
64use thiserror::Error;
65
66use crate::{
67    coroutine::*,
68    jmap_try,
69    rfc8620::{CORE_CAPABILITY, JmapBatch, JmapSession, send::*, set::*},
70    rfc8621::{
71        MAIL_CAPABILITY,
72        mailbox::{JmapMailbox, JmapMailboxCreate, JmapMailboxSetItemError, JmapMailboxUpdate},
73    },
74};
75
76/// Failure causes during a JMAP `Mailbox/set` flow.
77#[derive(Debug, Error)]
78pub enum JmapMailboxSetError {
79    #[error("JMAP Mailbox/set failed: {0}")]
80    Send(#[from] JmapSendError),
81    #[error("JMAP Mailbox/set failed: serialize args: {0}")]
82    SerializeArgs(#[source] serde_json::Error),
83    #[error("JMAP Mailbox/set failed: {0}")]
84    Set(#[from] JmapSetError),
85}
86
87/// Arguments for a `Mailbox/set` request.
88#[derive(Clone, Debug, Default, Serialize)]
89#[serde(rename_all = "camelCase")]
90pub struct JmapMailboxSetArgs {
91    /// Objects to create (client ID → partial mailbox object).
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub create: Option<BTreeMap<String, JmapMailboxCreate>>,
94
95    /// Objects to update (mailbox ID → patch object).
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub update: Option<BTreeMap<String, JmapMailboxUpdate>>,
98
99    /// IDs of objects to destroy.
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub destroy: Option<Vec<String>>,
102
103    /// Whether to destroy contained emails when destroying a mailbox.
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub on_destroy_remove_emails: Option<bool>,
106}
107
108/// Successful terminal output of [`JmapMailboxSet`].
109#[derive(Clone, Debug)]
110pub struct JmapMailboxSetOutput {
111    pub new_state: String,
112    pub created: BTreeMap<String, JmapMailbox>,
113    pub updated: BTreeMap<String, Option<JmapMailbox>>,
114    pub destroyed: Vec<String>,
115    pub not_created: BTreeMap<String, JmapMailboxSetItemError>,
116    pub not_updated: BTreeMap<String, JmapMailboxSetItemError>,
117    pub not_destroyed: BTreeMap<String, JmapMailboxSetItemError>,
118    pub keep_alive: bool,
119}
120
121/// I/O-free coroutine for the JMAP `Mailbox/set` method.
122pub struct JmapMailboxSet {
123    state: State,
124}
125
126impl JmapMailboxSet {
127    pub fn new(
128        session: &JmapSession,
129        http_auth: &SecretString,
130        args: JmapMailboxSetArgs,
131    ) -> Result<Self, JmapMailboxSetError> {
132        let account_id = session
133            .primary_accounts
134            .get(MAIL_CAPABILITY)
135            .cloned()
136            .unwrap_or_default();
137        let api_url = &session.api_url;
138
139        let json_args = serde_json::to_value(MailboxSetRequest { account_id, args })
140            .map_err(JmapMailboxSetError::SerializeArgs)?;
141
142        let mut batch = JmapBatch::new();
143        batch.add("Mailbox/set", json_args);
144        let request = batch.into_request(vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()]);
145
146        let send = JmapSend::new(http_auth, api_url, request)?;
147        Ok(Self {
148            state: State::Set(JmapSet::from_send(send)),
149        })
150    }
151}
152
153impl JmapCoroutine for JmapMailboxSet {
154    type Yield = JmapYield;
155    type Return = Result<JmapMailboxSetOutput, JmapMailboxSetError>;
156
157    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
158        trace!("Mailbox/set: {}", self.state);
159        match &mut self.state {
160            State::Set(set) => {
161                let JmapSetOutput {
162                    new_state,
163                    created,
164                    updated,
165                    destroyed,
166                    not_created,
167                    not_updated,
168                    not_destroyed,
169                    keep_alive,
170                } = jmap_try!(set, arg);
171                let parse = |map: BTreeMap<String, serde_json::Value>| {
172                    map.into_iter()
173                        .map(|(k, v)| {
174                            let e = serde_json::from_value(v)
175                                .unwrap_or(JmapMailboxSetItemError::Unknown);
176                            (k, e)
177                        })
178                        .collect()
179                };
180                JmapCoroutineState::Complete(Ok(JmapMailboxSetOutput {
181                    new_state,
182                    created,
183                    updated,
184                    destroyed,
185                    not_created: parse(not_created),
186                    not_updated: parse(not_updated),
187                    not_destroyed: parse(not_destroyed),
188                    keep_alive,
189                }))
190            }
191        }
192    }
193}
194
195enum State {
196    Set(JmapSet<JmapMailbox>),
197}
198
199impl fmt::Display for State {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Self::Set(_) => f.write_str("set"),
203        }
204    }
205}
206
207#[derive(Serialize)]
208struct MailboxSetRequest {
209    #[serde(rename = "accountId")]
210    account_id: String,
211    #[serde(flatten)]
212    args: JmapMailboxSetArgs,
213}