Skip to main content

io_jmap/rfc8621/thread/
changes.rs

1//! JMAP `Thread/changes` coroutine (RFC 8621 ยง3.2): wraps the generic
2//! [`JmapChanges`] with the JMAP-Mail capability set.
3//!
4//! # Example
5//!
6//! ```rust,no_run
7//! use std::{
8//!     io::{Read, Write},
9//!     net::TcpStream,
10//! };
11//!
12//! use io_jmap::{
13//!     coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
14//!     rfc8620::JmapSession,
15//!     rfc8621::thread::changes::{JmapThreadChanges, JmapThreadChangesOptions},
16//! };
17//! use secrecy::SecretString;
18//!
19//! // Ready stream needed (TCP-connected, TLS-negociated)
20//! let mut stream = TcpStream::connect("api.example.com:443").unwrap();
21//! let mut buf = [0u8; 4096];
22//!
23//! let session: JmapSession = serde_json::from_str(r#"{
24//!     "username": "",
25//!     "accounts": {},
26//!     "primaryAccounts": {"urn:ietf:params:jmap:mail": "a1"},
27//!     "capabilities": {},
28//!     "apiUrl": "https://api.example.com/jmap/",
29//!     "downloadUrl": "",
30//!     "uploadUrl": "",
31//!     "eventSourceUrl": "",
32//!     "state": ""
33//! }"#).unwrap();
34//! let auth = SecretString::from("Bearer xyz");
35//! let mut coroutine =
36//!     JmapThreadChanges::new(&session, &auth, "s1", JmapThreadChangesOptions::default())
37//!         .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::{string::String, vec};
60
61use log::trace;
62use secrecy::SecretString;
63use thiserror::Error;
64
65use crate::{
66    coroutine::*,
67    jmap_try,
68    rfc8620::{CORE_CAPABILITY, JmapSession, changes::*},
69    rfc8621::MAIL_CAPABILITY,
70};
71
72/// Failure causes during a JMAP `Thread/changes` flow.
73#[derive(Debug, Error)]
74pub enum JmapThreadChangesError {
75    #[error("JMAP Thread/changes failed: {0}")]
76    Changes(#[from] JmapChangesError),
77}
78
79/// Options for [`JmapThreadChanges::new`].
80#[derive(Clone, Debug, Default)]
81pub struct JmapThreadChangesOptions {
82    /// Server-side cap on the number of changes returned.
83    pub max_changes: Option<u64>,
84}
85
86/// I/O-free coroutine for the JMAP `Thread/changes` method.
87pub struct JmapThreadChanges {
88    state: State,
89}
90
91impl JmapThreadChanges {
92    pub fn new(
93        session: &JmapSession,
94        http_auth: &SecretString,
95        since_state: impl Into<String>,
96        opts: JmapThreadChangesOptions,
97    ) -> Result<Self, JmapThreadChangesError> {
98        let account_id = session
99            .primary_accounts
100            .get(MAIL_CAPABILITY)
101            .cloned()
102            .unwrap_or_default();
103        let api_url = &session.api_url;
104
105        Ok(Self {
106            state: State::Changes(JmapChanges::new(
107                account_id,
108                http_auth,
109                api_url,
110                "Thread/changes",
111                vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()],
112                since_state,
113                JmapChangesOptions {
114                    max_changes: opts.max_changes,
115                },
116            )?),
117        })
118    }
119}
120
121impl JmapCoroutine for JmapThreadChanges {
122    type Yield = JmapYield;
123    type Return = Result<JmapChangesOutput, JmapThreadChangesError>;
124
125    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
126        trace!("Thread/changes: {}", self.state);
127        match &mut self.state {
128            State::Changes(changes) => {
129                let out = jmap_try!(changes, arg);
130                JmapCoroutineState::Complete(Ok(out))
131            }
132        }
133    }
134}
135
136enum State {
137    Changes(JmapChanges),
138}
139
140impl fmt::Display for State {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        match self {
143            Self::Changes(_) => f.write_str("changes"),
144        }
145    }
146}