Skip to main content

io_jmap/rfc8621/email_submission/
get.rs

1//! JMAP `EmailSubmission/get` coroutine (RFC 8621 ยง7.2): wraps the
2//! generic [`JmapGet`] with the Submission capability.
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::email_submission::get::{
16//!         JmapEmailSubmissionGet, JmapEmailSubmissionGetOptions,
17//!     },
18//! };
19//! use secrecy::SecretString;
20//!
21//! // Ready stream needed (TCP-connected, TLS-negociated)
22//! let mut stream = TcpStream::connect("api.example.com:443").unwrap();
23//! let mut buf = [0u8; 4096];
24//!
25//! let session: JmapSession = serde_json::from_str(r#"{
26//!     "username": "",
27//!     "accounts": {},
28//!     "primaryAccounts": {"urn:ietf:params:jmap:mail": "a1"},
29//!     "capabilities": {},
30//!     "apiUrl": "https://api.example.com/jmap/",
31//!     "downloadUrl": "",
32//!     "uploadUrl": "",
33//!     "eventSourceUrl": "",
34//!     "state": ""
35//! }"#).unwrap();
36//! let auth = SecretString::from("Bearer xyz");
37//! let mut coroutine = JmapEmailSubmissionGet::new(
38//!     &session,
39//!     &auth,
40//!     JmapEmailSubmissionGetOptions::default(),
41//! )
42//! .unwrap();
43//! let mut arg = None;
44//!
45//! let out = loop {
46//!     match coroutine.resume(arg.take()) {
47//!         JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
48//!             stream.write_all(&bytes).unwrap();
49//!         }
50//!         JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
51//!             let n = stream.read(&mut buf).unwrap();
52//!             arg = Some(&buf[..n]);
53//!         }
54//!         JmapCoroutineState::Complete(Ok(out)) => break out,
55//!         JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
56//!     }
57//! };
58//!
59//! println!("{} submissions", out.submissions.len());
60//! ```
61
62use alloc::{string::String, vec, vec::Vec};
63
64use secrecy::SecretString;
65use thiserror::Error;
66
67use crate::{
68    coroutine::*,
69    jmap_try,
70    rfc8620::{JMAP_CORE_CAPABILITY, get::*, session::JmapSession},
71    rfc8621::{
72        JMAP_MAIL_CAPABILITY,
73        email_submission::{JMAP_SUBMISSION_CAPABILITY, JmapEmailSubmission},
74    },
75};
76
77/// Failure causes during a JMAP `EmailSubmission/get` flow.
78#[derive(Debug, Error)]
79pub enum JmapEmailSubmissionGetError {
80    /// The inner generic get coroutine failed.
81    #[error("JMAP EmailSubmission/get failed: {0}")]
82    Get(#[from] JmapGetError),
83}
84
85/// Options for [`JmapEmailSubmissionGet::new`].
86#[derive(Clone, Debug, Default)]
87pub struct JmapEmailSubmissionGetOptions {
88    /// Restrict the fetch to these submission IDs; `None` fetches all.
89    pub ids: Option<Vec<String>>,
90}
91
92/// Successful terminal output of [`JmapEmailSubmissionGet`].
93#[derive(Clone, Debug)]
94pub struct JmapEmailSubmissionGetOutput {
95    /// The fetched email submissions.
96    pub submissions: Vec<JmapEmailSubmission>,
97    /// The requested ids the server did not find.
98    pub not_found: Vec<String>,
99    /// The new server state after the call.
100    pub new_state: String,
101    /// Whether the server indicated the connection can be reused.
102    pub keep_alive: bool,
103}
104
105/// I/O-free coroutine for the JMAP `EmailSubmission/get` method.
106pub struct JmapEmailSubmissionGet {
107    state: State,
108}
109
110impl JmapEmailSubmissionGet {
111    /// Prepares the method call request and builds the coroutine.
112    pub fn new(
113        session: &JmapSession,
114        http_auth: &SecretString,
115        opts: JmapEmailSubmissionGetOptions,
116    ) -> Result<Self, JmapEmailSubmissionGetError> {
117        let account_id = session
118            .primary_accounts
119            .get(JMAP_MAIL_CAPABILITY)
120            .cloned()
121            .unwrap_or_default();
122        let api_url = &session.api_url;
123
124        Ok(Self {
125            state: State::Get(JmapGet::new(
126                account_id,
127                http_auth,
128                api_url,
129                "EmailSubmission/get",
130                vec![
131                    JMAP_CORE_CAPABILITY.into(),
132                    JMAP_MAIL_CAPABILITY.into(),
133                    JMAP_SUBMISSION_CAPABILITY.into(),
134                ],
135                JmapGetOptions {
136                    ids: opts.ids,
137                    properties: None,
138                },
139            )?),
140        })
141    }
142}
143
144impl JmapCoroutine for JmapEmailSubmissionGet {
145    type Yield = JmapYield;
146    type Return = Result<JmapEmailSubmissionGetOutput, JmapEmailSubmissionGetError>;
147
148    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
149        match &mut self.state {
150            State::Get(get) => {
151                let JmapGetOutput {
152                    list,
153                    not_found,
154                    state,
155                    keep_alive,
156                } = jmap_try!(get, arg);
157                JmapCoroutineState::Complete(Ok(JmapEmailSubmissionGetOutput {
158                    submissions: list,
159                    not_found,
160                    new_state: state,
161                    keep_alive,
162                }))
163            }
164        }
165    }
166}
167
168enum State {
169    Get(JmapGet<JmapEmailSubmission>),
170}