Skip to main content

io_jmap/calendars/calendar_event/
changes.rs

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