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::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 core::fmt;
56
57use alloc::{string::String, vec, vec::Vec};
58
59use log::trace;
60use secrecy::SecretString;
61use thiserror::Error;
62
63use crate::{
64    coroutine::*,
65    jmap_try,
66    rfc8620::{CORE_CAPABILITY, JmapSession, get::*},
67    rfc8621::{MAIL_CAPABILITY, thread::JmapThread},
68};
69
70/// Failure causes during a JMAP `Thread/get` flow.
71#[derive(Debug, Error)]
72pub enum JmapThreadGetError {
73    #[error("JMAP Thread/get failed: {0}")]
74    Get(#[from] JmapGetError),
75}
76
77/// Successful terminal output of [`JmapThreadGet`].
78#[derive(Clone, Debug)]
79pub struct JmapThreadGetOutput {
80    pub threads: Vec<JmapThread>,
81    pub not_found: Vec<String>,
82    pub new_state: String,
83    pub keep_alive: bool,
84}
85
86/// I/O-free coroutine for the JMAP `Thread/get` method.
87pub struct JmapThreadGet {
88    state: State,
89}
90
91impl JmapThreadGet {
92    pub fn new(
93        session: &JmapSession,
94        http_auth: &SecretString,
95        ids: Vec<String>,
96    ) -> Result<Self, JmapThreadGetError> {
97        let account_id = session
98            .primary_accounts
99            .get(MAIL_CAPABILITY)
100            .cloned()
101            .unwrap_or_default();
102        let api_url = &session.api_url;
103
104        Ok(Self {
105            state: State::Get(JmapGet::new(
106                account_id,
107                http_auth,
108                api_url,
109                "Thread/get",
110                vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()],
111                JmapGetOptions {
112                    ids: Some(ids),
113                    properties: None,
114                },
115            )?),
116        })
117    }
118}
119
120impl JmapCoroutine for JmapThreadGet {
121    type Yield = JmapYield;
122    type Return = Result<JmapThreadGetOutput, JmapThreadGetError>;
123
124    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
125        trace!("Thread/get: {}", self.state);
126        match &mut self.state {
127            State::Get(get) => {
128                let JmapGetOutput {
129                    list,
130                    not_found,
131                    state,
132                    keep_alive,
133                } = jmap_try!(get, arg);
134                JmapCoroutineState::Complete(Ok(JmapThreadGetOutput {
135                    threads: list,
136                    not_found,
137                    new_state: state,
138                    keep_alive,
139                }))
140            }
141        }
142    }
143}
144
145enum State {
146    Get(JmapGet<JmapThread>),
147}
148
149impl fmt::Display for State {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Self::Get(_) => f.write_str("get"),
153        }
154    }
155}