Skip to main content

io_jmap/rfc8621/vacation_response/
get.rs

1//! JMAP `VacationResponse/get` coroutine (RFC 8621 ยง8.2): wraps the generic
2//! [`JmapGet`] for the singleton `JmapVacationResponse` object.
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::vacation_response::get::JmapVacationResponseGet,
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 = JmapVacationResponseGet::new(&session, &auth).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!("vacation enabled: {:?}", out.vacation_response);
53//! ```
54
55use core::fmt;
56
57use alloc::{
58    string::{String, ToString},
59    vec,
60    vec::Vec,
61};
62
63use log::trace;
64use secrecy::SecretString;
65use serde::Serialize;
66use thiserror::Error;
67
68use crate::{
69    coroutine::*,
70    jmap_try,
71    rfc8620::{CORE_CAPABILITY, JmapBatch, JmapSession, get::*, send::*},
72    rfc8621::{
73        MAIL_CAPABILITY,
74        vacation_response::{JmapVacationResponse, VACATION_RESPONSE_CAPABILITY},
75    },
76};
77
78/// Failure causes during a JMAP `VacationResponse/get` flow.
79#[derive(Debug, Error)]
80pub enum JmapVacationResponseGetError {
81    #[error("JMAP VacationResponse/get failed: {0}")]
82    Send(#[from] JmapSendError),
83    #[error("JMAP VacationResponse/get failed: serialize args: {0}")]
84    SerializeArgs(#[source] serde_json::Error),
85    #[error("JMAP VacationResponse/get failed: {0}")]
86    Get(#[from] JmapGetError),
87}
88
89/// Successful terminal output of [`JmapVacationResponseGet`].
90#[derive(Clone, Debug)]
91pub struct JmapVacationResponseGetOutput {
92    pub vacation_response: Option<JmapVacationResponse>,
93    pub new_state: String,
94    pub keep_alive: bool,
95}
96
97/// I/O-free coroutine for the JMAP `VacationResponse/get` method.
98pub struct JmapVacationResponseGet {
99    state: State,
100}
101
102impl JmapVacationResponseGet {
103    pub fn new(
104        session: &JmapSession,
105        http_auth: &SecretString,
106    ) -> Result<Self, JmapVacationResponseGetError> {
107        let account_id = session
108            .primary_accounts
109            .get(VACATION_RESPONSE_CAPABILITY)
110            .or_else(|| session.primary_accounts.get(MAIL_CAPABILITY))
111            .cloned()
112            .unwrap_or_default();
113        let api_url = &session.api_url;
114
115        let args = serde_json::to_value(VacationResponseGetArgs {
116            account_id,
117            ids: vec!["singleton".to_string()],
118        })
119        .map_err(JmapVacationResponseGetError::SerializeArgs)?;
120
121        let mut using = vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()];
122        if session
123            .capabilities
124            .contains_key(VACATION_RESPONSE_CAPABILITY)
125        {
126            using.push(VACATION_RESPONSE_CAPABILITY.into());
127        }
128
129        let mut batch = JmapBatch::new();
130        batch.add("VacationResponse/get", args);
131        let request = batch.into_request(using);
132
133        let send = JmapSend::new(http_auth, api_url, request)?;
134        Ok(Self {
135            state: State::Get(JmapGet::from_send(send)),
136        })
137    }
138}
139
140impl JmapCoroutine for JmapVacationResponseGet {
141    type Yield = JmapYield;
142    type Return = Result<JmapVacationResponseGetOutput, JmapVacationResponseGetError>;
143
144    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
145        trace!("VacationResponse/get: {}", self.state);
146        match &mut self.state {
147            State::Get(get) => {
148                let JmapGetOutput {
149                    list,
150                    state,
151                    keep_alive,
152                    ..
153                } = jmap_try!(get, arg);
154                JmapCoroutineState::Complete(Ok(JmapVacationResponseGetOutput {
155                    vacation_response: list.into_iter().next(),
156                    new_state: state,
157                    keep_alive,
158                }))
159            }
160        }
161    }
162}
163
164enum State {
165    Get(JmapGet<JmapVacationResponse>),
166}
167
168impl fmt::Display for State {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            Self::Get(_) => f.write_str("get"),
172        }
173    }
174}
175
176#[derive(Serialize)]
177#[serde(rename_all = "camelCase")]
178struct VacationResponseGetArgs {
179    account_id: String,
180    ids: Vec<String>,
181}