io_jmap/calendars/calendar/get.rs
1//! JMAP `Calendar/get` coroutine (draft-ietf-jmap-calendars-27): wraps
2//! the generic [`JmapGet`] with the JMAP-Calendars capability set.
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//! calendars::calendar::get::{JmapCalendarGet, JmapCalendarGetOptions},
14//! coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
15//! rfc8620::session::JmapSession,
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:calendars": "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 =
36//! JmapCalendarGet::new(&session, &auth, JmapCalendarGetOptions::default()).unwrap();
37//! let mut arg = None;
38//!
39//! let out = loop {
40//! match coroutine.resume(arg.take()) {
41//! JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
42//! stream.write_all(&bytes).unwrap();
43//! }
44//! JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
45//! let n = stream.read(&mut buf).unwrap();
46//! arg = Some(&buf[..n]);
47//! }
48//! JmapCoroutineState::Complete(Ok(out)) => break out,
49//! JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
50//! }
51//! };
52//!
53//! println!("{} calendars", out.calendars.len());
54//! ```
55
56use core::fmt;
57
58use alloc::{
59 string::{String, ToString},
60 vec,
61 vec::Vec,
62};
63
64use secrecy::SecretString;
65use serde::{Serialize, Serializer};
66use thiserror::Error;
67
68use crate::{
69 calendars::{JMAP_CALENDARS_CAPABILITY, calendar::JmapCalendar},
70 coroutine::*,
71 jmap_try,
72 rfc8620::{JMAP_CORE_CAPABILITY, get::*, session::JmapSession},
73};
74
75/// [`JmapCalendar`] properties requestable in `Calendar/get`
76/// (draft-ietf-jmap-calendars).
77#[derive(Clone, Debug, PartialEq, Eq)]
78pub enum JmapCalendarProperty {
79 /// The `id` property.
80 Id,
81 /// The `name` property.
82 Name,
83 /// The `description` property.
84 Description,
85 /// The `color` property.
86 Color,
87 /// The `sortOrder` property.
88 SortOrder,
89 /// The `isDefault` property.
90 IsDefault,
91 /// The `isSubscribed` property.
92 IsSubscribed,
93 /// The `isVisible` property.
94 IsVisible,
95 /// The `includeInAvailability` property.
96 IncludeInAvailability,
97 /// The `defaultAlertsWithTime` property.
98 DefaultAlertsWithTime,
99 /// The `defaultAlertsWithoutTime` property.
100 DefaultAlertsWithoutTime,
101 /// The `timeZone` property.
102 TimeZone,
103 /// The `shareWith` property.
104 ShareWith,
105 /// The `myRights` property.
106 MyRights,
107}
108
109impl fmt::Display for JmapCalendarProperty {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.write_str(match self {
112 Self::Id => "id",
113 Self::Name => "name",
114 Self::Description => "description",
115 Self::Color => "color",
116 Self::SortOrder => "sortOrder",
117 Self::IsDefault => "isDefault",
118 Self::IsSubscribed => "isSubscribed",
119 Self::IsVisible => "isVisible",
120 Self::IncludeInAvailability => "includeInAvailability",
121 Self::DefaultAlertsWithTime => "defaultAlertsWithTime",
122 Self::DefaultAlertsWithoutTime => "defaultAlertsWithoutTime",
123 Self::TimeZone => "timeZone",
124 Self::ShareWith => "shareWith",
125 Self::MyRights => "myRights",
126 })
127 }
128}
129
130impl Serialize for JmapCalendarProperty {
131 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
132 s.collect_str(self)
133 }
134}
135
136/// Failure causes during a JMAP `Calendar/get` flow.
137#[derive(Debug, Error)]
138pub enum JmapCalendarGetError {
139 /// The inner generic get coroutine failed.
140 #[error("JMAP Calendar/get failed: {0}")]
141 Get(#[from] JmapGetError),
142}
143
144/// Options for [`JmapCalendarGet::new`].
145#[derive(Clone, Debug, Default)]
146pub struct JmapCalendarGetOptions {
147 /// Restrict the fetch to these Calendar IDs; `None` fetches all.
148 pub ids: Option<Vec<String>>,
149 /// Restrict the returned properties; `None` returns all.
150 pub properties: Option<Vec<JmapCalendarProperty>>,
151}
152
153/// Successful terminal output of [`JmapCalendarGet`].
154#[derive(Clone, Debug)]
155pub struct JmapCalendarGetOutput {
156 /// The fetched calendars.
157 pub calendars: Vec<JmapCalendar>,
158 /// The requested ids the server did not find.
159 pub not_found: Vec<String>,
160 /// The new server state after the call.
161 pub new_state: String,
162 /// Whether the server indicated the connection can be reused.
163 pub keep_alive: bool,
164}
165
166/// I/O-free coroutine for the JMAP `Calendar/get` method.
167pub struct JmapCalendarGet {
168 state: State,
169}
170
171impl JmapCalendarGet {
172 /// Prepares the method call request and builds the coroutine.
173 pub fn new(
174 session: &JmapSession,
175 http_auth: &SecretString,
176 opts: JmapCalendarGetOptions,
177 ) -> Result<Self, JmapCalendarGetError> {
178 let account_id = session
179 .primary_accounts
180 .get(JMAP_CALENDARS_CAPABILITY)
181 .cloned()
182 .unwrap_or_default();
183 let api_url = &session.api_url;
184
185 let properties = opts
186 .properties
187 .map(|props| props.iter().map(ToString::to_string).collect());
188
189 Ok(Self {
190 state: State::Get(JmapGet::new(
191 account_id,
192 http_auth,
193 api_url,
194 "Calendar/get",
195 vec![
196 JMAP_CORE_CAPABILITY.into(),
197 JMAP_CALENDARS_CAPABILITY.into(),
198 ],
199 JmapGetOptions {
200 ids: opts.ids,
201 properties,
202 },
203 )?),
204 })
205 }
206}
207
208impl JmapCoroutine for JmapCalendarGet {
209 type Yield = JmapYield;
210 type Return = Result<JmapCalendarGetOutput, JmapCalendarGetError>;
211
212 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
213 match &mut self.state {
214 State::Get(get) => {
215 let JmapGetOutput {
216 list,
217 not_found,
218 state,
219 keep_alive,
220 } = jmap_try!(get, arg);
221 JmapCoroutineState::Complete(Ok(JmapCalendarGetOutput {
222 calendars: list,
223 not_found,
224 new_state: state,
225 keep_alive,
226 }))
227 }
228 }
229 }
230}
231
232enum State {
233 Get(JmapGet<JmapCalendar>),
234}