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::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 core::fmt;
63
64use alloc::{string::String, vec, vec::Vec};
65
66use log::trace;
67use secrecy::SecretString;
68use thiserror::Error;
69
70use crate::{
71 coroutine::*,
72 jmap_try,
73 rfc8620::{CORE_CAPABILITY, JmapSession, get::*},
74 rfc8621::{
75 MAIL_CAPABILITY,
76 email_submission::{JmapEmailSubmission, SUBMISSION_CAPABILITY},
77 },
78};
79
80/// Failure causes during a JMAP `EmailSubmission/get` flow.
81#[derive(Debug, Error)]
82pub enum JmapEmailSubmissionGetError {
83 #[error("JMAP EmailSubmission/get failed: {0}")]
84 Get(#[from] JmapGetError),
85}
86
87/// Options for [`JmapEmailSubmissionGet::new`].
88#[derive(Clone, Debug, Default)]
89pub struct JmapEmailSubmissionGetOptions {
90 /// Restrict the fetch to these submission IDs; `None` fetches all.
91 pub ids: Option<Vec<String>>,
92}
93
94/// Successful terminal output of [`JmapEmailSubmissionGet`].
95#[derive(Clone, Debug)]
96pub struct JmapEmailSubmissionGetOutput {
97 pub submissions: Vec<JmapEmailSubmission>,
98 pub not_found: Vec<String>,
99 pub new_state: String,
100 pub keep_alive: bool,
101}
102
103/// I/O-free coroutine for the JMAP `EmailSubmission/get` method.
104pub struct JmapEmailSubmissionGet {
105 state: State,
106}
107
108impl JmapEmailSubmissionGet {
109 pub fn new(
110 session: &JmapSession,
111 http_auth: &SecretString,
112 opts: JmapEmailSubmissionGetOptions,
113 ) -> Result<Self, JmapEmailSubmissionGetError> {
114 let account_id = session
115 .primary_accounts
116 .get(MAIL_CAPABILITY)
117 .cloned()
118 .unwrap_or_default();
119 let api_url = &session.api_url;
120
121 Ok(Self {
122 state: State::Get(JmapGet::new(
123 account_id,
124 http_auth,
125 api_url,
126 "EmailSubmission/get",
127 vec![
128 CORE_CAPABILITY.into(),
129 MAIL_CAPABILITY.into(),
130 SUBMISSION_CAPABILITY.into(),
131 ],
132 JmapGetOptions {
133 ids: opts.ids,
134 properties: None,
135 },
136 )?),
137 })
138 }
139}
140
141impl JmapCoroutine for JmapEmailSubmissionGet {
142 type Yield = JmapYield;
143 type Return = Result<JmapEmailSubmissionGetOutput, JmapEmailSubmissionGetError>;
144
145 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
146 trace!("EmailSubmission/get: {}", self.state);
147 match &mut self.state {
148 State::Get(get) => {
149 let JmapGetOutput {
150 list,
151 not_found,
152 state,
153 keep_alive,
154 } = jmap_try!(get, arg);
155 JmapCoroutineState::Complete(Ok(JmapEmailSubmissionGetOutput {
156 submissions: list,
157 not_found,
158 new_state: state,
159 keep_alive,
160 }))
161 }
162 }
163 }
164}
165
166enum State {
167 Get(JmapGet<JmapEmailSubmission>),
168}
169
170impl fmt::Display for State {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 match self {
173 Self::Get(_) => f.write_str("get"),
174 }
175 }
176}