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}