Skip to main content

io_jmap/calendars/calendar/
changes.rs

1//! JMAP `Calendar/changes` coroutine (draft-ietf-jmap-calendars-27):
2//! wraps the generic [`JmapChanges`] with the JMAP-Calendars capability
3//! set.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! use std::{
9//!     io::{Read, Write},
10//!     net::TcpStream,
11//! };
12//!
13//! use io_jmap::{
14//!     calendars::calendar::changes::{JmapCalendarChanges, JmapCalendarChangesOptions},
15//!     coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
16//!     rfc8620::session::JmapSession,
17//! };
18//! use secrecy::SecretString;
19//!
20//! // Ready stream needed (TCP-connected, TLS-negociated)
21//! let mut stream = TcpStream::connect("api.example.com:443").unwrap();
22//! let mut buf = [0u8; 4096];
23//!
24//! let session: JmapSession = serde_json::from_str(r#"{
25//!     "username": "",
26//!     "accounts": {},
27//!     "primaryAccounts": {"urn:ietf:params:jmap:calendars": "a1"},
28//!     "capabilities": {},
29//!     "apiUrl": "https://api.example.com/jmap/",
30//!     "downloadUrl": "",
31//!     "uploadUrl": "",
32//!     "eventSourceUrl": "",
33//!     "state": ""
34//! }"#).unwrap();
35//! let auth = SecretString::from("Bearer xyz");
36//! let mut coroutine =
37//!     JmapCalendarChanges::new(&session, &auth, "s1", JmapCalendarChangesOptions::default())
38//!         .unwrap();
39//! let mut arg = None;
40//!
41//! let out = loop {
42//!     match coroutine.resume(arg.take()) {
43//!         JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
44//!             stream.write_all(&bytes).unwrap();
45//!         }
46//!         JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
47//!             let n = stream.read(&mut buf).unwrap();
48//!             arg = Some(&buf[..n]);
49//!         }
50//!         JmapCoroutineState::Complete(Ok(out)) => break out,
51//!         JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
52//!     }
53//! };
54//!
55//! println!("new state {}", out.new_state);
56//! ```
57
58use alloc::{string::String, vec};
59
60use secrecy::SecretString;
61use thiserror::Error;
62
63use crate::{
64    calendars::JMAP_CALENDARS_CAPABILITY,
65    coroutine::*,
66    jmap_try,
67    rfc8620::{JMAP_CORE_CAPABILITY, changes::*, session::JmapSession},
68};
69
70/// Failure causes during a JMAP `Calendar/changes` flow.
71#[derive(Debug, Error)]
72pub enum JmapCalendarChangesError {
73    /// The inner generic changes coroutine failed.
74    #[error("JMAP Calendar/changes failed: {0}")]
75    Changes(#[from] JmapChangesError),
76}
77
78/// Options for [`JmapCalendarChanges::new`].
79#[derive(Clone, Debug, Default)]
80pub struct JmapCalendarChangesOptions {
81    /// Server-side cap on the number of changes returned.
82    pub max_changes: Option<u64>,
83}
84
85/// I/O-free coroutine for the JMAP `Calendar/changes` method.
86pub struct JmapCalendarChanges {
87    state: State,
88}
89
90impl JmapCalendarChanges {
91    /// Prepares the method call request and builds the coroutine.
92    pub fn new(
93        session: &JmapSession,
94        http_auth: &SecretString,
95        since_state: impl Into<String>,
96        opts: JmapCalendarChangesOptions,
97    ) -> Result<Self, JmapCalendarChangesError> {
98        let account_id = session
99            .primary_accounts
100            .get(JMAP_CALENDARS_CAPABILITY)
101            .cloned()
102            .unwrap_or_default();
103        let api_url = &session.api_url;
104
105        Ok(Self {
106            state: State::Changes(JmapChanges::new(
107                account_id,
108                http_auth,
109                api_url,
110                "Calendar/changes",
111                vec![
112                    JMAP_CORE_CAPABILITY.into(),
113                    JMAP_CALENDARS_CAPABILITY.into(),
114                ],
115                since_state,
116                JmapChangesOptions {
117                    max_changes: opts.max_changes,
118                },
119            )?),
120        })
121    }
122}
123
124impl JmapCoroutine for JmapCalendarChanges {
125    type Yield = JmapYield;
126    type Return = Result<JmapChangesOutput, JmapCalendarChangesError>;
127
128    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
129        match &mut self.state {
130            State::Changes(changes) => {
131                let out = jmap_try!(changes, arg);
132                JmapCoroutineState::Complete(Ok(out))
133            }
134        }
135    }
136}
137
138enum State {
139    Changes(JmapChanges),
140}