Skip to main content

io_jmap/rfc8621/thread/
get.rs

1//! JMAP `Thread/get` coroutine (RFC 8621 ยง3.3): wraps the generic [`JmapGet`]
2//! 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::session::JmapSession,
15//!     rfc8621::thread::get::JmapThreadGet,
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 = JmapThreadGet::new(&session, &auth, vec!["t1".into()]).unwrap();
36//! let mut arg = None;
37//!
38//! let out = loop {
39//!     match coroutine.resume(arg.take()) {
40//!         JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
41//!             stream.write_all(&bytes).unwrap();
42//!         }
43//!         JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
44//!             let n = stream.read(&mut buf).unwrap();
45//!             arg = Some(&buf[..n]);
46//!         }
47//!         JmapCoroutineState::Complete(Ok(out)) => break out,
48//!         JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
49//!     }
50//! };
51//!
52//! println!("{} threads", out.threads.len());
53//! ```
54
55use alloc::{string::String, vec, vec::Vec};
56
57use secrecy::SecretString;
58use thiserror::Error;
59
60use crate::{
61    coroutine::*,
62    jmap_try,
63    rfc8620::{JMAP_CORE_CAPABILITY, get::*, session::JmapSession},
64    rfc8621::{JMAP_MAIL_CAPABILITY, thread::JmapThread},
65};
66
67/// Failure causes during a JMAP `Thread/get` flow.
68#[derive(Debug, Error)]
69pub enum JmapThreadGetError {
70    /// The inner generic get coroutine failed.
71    #[error("JMAP Thread/get failed: {0}")]
72    Get(#[from] JmapGetError),
73}
74
75/// Successful terminal output of [`JmapThreadGet`].
76#[derive(Clone, Debug)]
77pub struct JmapThreadGetOutput {
78    /// The fetched threads.
79    pub threads: Vec<JmapThread>,
80    /// The requested ids the server did not find.
81    pub not_found: Vec<String>,
82    /// The new server state after the call.
83    pub new_state: String,
84    /// Whether the server indicated the connection can be reused.
85    pub keep_alive: bool,
86}
87
88/// I/O-free coroutine for the JMAP `Thread/get` method.
89pub struct JmapThreadGet {
90    state: State,
91}
92
93impl JmapThreadGet {
94    /// Prepares the method call request and builds the coroutine.
95    pub fn new(
96        session: &JmapSession,
97        http_auth: &SecretString,
98        ids: Vec<String>,
99    ) -> Result<Self, JmapThreadGetError> {
100        let account_id = session
101            .primary_accounts
102            .get(JMAP_MAIL_CAPABILITY)
103            .cloned()
104            .unwrap_or_default();
105        let api_url = &session.api_url;
106
107        Ok(Self {
108            state: State::Get(JmapGet::new(
109                account_id,
110                http_auth,
111                api_url,
112                "Thread/get",
113                vec![JMAP_CORE_CAPABILITY.into(), JMAP_MAIL_CAPABILITY.into()],
114                JmapGetOptions {
115                    ids: Some(ids),
116                    properties: None,
117                },
118            )?),
119        })
120    }
121}
122
123impl JmapCoroutine for JmapThreadGet {
124    type Yield = JmapYield;
125    type Return = Result<JmapThreadGetOutput, JmapThreadGetError>;
126
127    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
128        match &mut self.state {
129            State::Get(get) => {
130                let JmapGetOutput {
131                    list,
132                    not_found,
133                    state,
134                    keep_alive,
135                } = jmap_try!(get, arg);
136                JmapCoroutineState::Complete(Ok(JmapThreadGetOutput {
137                    threads: list,
138                    not_found,
139                    new_state: state,
140                    keep_alive,
141                }))
142            }
143        }
144    }
145}
146
147enum State {
148    Get(JmapGet<JmapThread>),
149}