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}