Skip to main content

io_jmap/rfc9610/address_book/
get.rs

1//! JMAP `AddressBook/get` coroutine (RFC 9610 §2.1): wraps the generic
2//! [`JmapGet`] with the JMAP-Contacts 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//!     coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
14//!     rfc8620::session::JmapSession,
15//!     rfc9610::address_book::get::{JmapAddressBookGet, JmapAddressBookGetOptions},
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:contacts": "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//!     JmapAddressBookGet::new(&session, &auth, JmapAddressBookGetOptions::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!("{} address books", out.address_books.len());
54//! ```
55
56use alloc::{borrow::ToOwned, format, string::String, vec, vec::Vec};
57
58use secrecy::SecretString;
59use serde::Serialize;
60use thiserror::Error;
61
62use crate::{
63    coroutine::*,
64    jmap_try,
65    rfc8620::{JMAP_CORE_CAPABILITY, get::*, session::JmapSession},
66    rfc9610::{JMAP_CONTACTS_CAPABILITY, address_book::JmapAddressBook},
67};
68
69/// [`JmapAddressBook`] properties requestable in `AddressBook/get`
70/// (RFC 9610 §2).
71#[derive(Clone, Debug, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub enum JmapAddressBookProperty {
74    /// The `id` property.
75    Id,
76    /// The `name` property.
77    Name,
78    /// The `description` property.
79    Description,
80    /// The `sortOrder` property.
81    SortOrder,
82    /// The `isDefault` property.
83    IsDefault,
84    /// The `isSubscribed` property.
85    IsSubscribed,
86    /// The `shareWith` property.
87    ShareWith,
88    /// The `myRights` property.
89    MyRights,
90}
91
92/// Failure causes during a JMAP `AddressBook/get` flow.
93#[derive(Debug, Error)]
94pub enum JmapAddressBookGetError {
95    /// The inner generic get coroutine failed.
96    #[error("JMAP AddressBook/get failed: {0}")]
97    Get(#[from] JmapGetError),
98}
99
100/// Options for [`JmapAddressBookGet::new`].
101#[derive(Clone, Debug, Default)]
102pub struct JmapAddressBookGetOptions {
103    /// Restrict the fetch to these AddressBook IDs; `None` fetches all.
104    pub ids: Option<Vec<String>>,
105    /// Restrict the returned properties; `None` returns all.
106    pub properties: Option<Vec<JmapAddressBookProperty>>,
107}
108
109/// Successful terminal output of [`JmapAddressBookGet`].
110#[derive(Clone, Debug)]
111pub struct JmapAddressBookGetOutput {
112    /// The fetched address books.
113    pub address_books: Vec<JmapAddressBook>,
114    /// The requested ids the server did not find.
115    pub not_found: Vec<String>,
116    /// The new server state after the call.
117    pub new_state: String,
118    /// Whether the server indicated the connection can be reused.
119    pub keep_alive: bool,
120}
121
122/// I/O-free coroutine for the JMAP `AddressBook/get` method.
123pub struct JmapAddressBookGet {
124    state: State,
125}
126
127impl JmapAddressBookGet {
128    /// Prepares the method call request and builds the coroutine.
129    pub fn new(
130        session: &JmapSession,
131        http_auth: &SecretString,
132        opts: JmapAddressBookGetOptions,
133    ) -> Result<Self, JmapAddressBookGetError> {
134        let account_id = session
135            .primary_accounts
136            .get(JMAP_CONTACTS_CAPABILITY)
137            .cloned()
138            .unwrap_or_default();
139        let api_url = &session.api_url;
140
141        let props = opts.properties.map(|ps| {
142            ps.iter()
143                .map(|p| {
144                    serde_json::to_value(p)
145                        .ok()
146                        .and_then(|v| v.as_str().map(str::to_owned))
147                        .unwrap_or_else(|| format!("{p:?}"))
148                })
149                .collect()
150        });
151
152        Ok(Self {
153            state: State::Get(JmapGet::new(
154                account_id,
155                http_auth,
156                api_url,
157                "AddressBook/get",
158                vec![JMAP_CORE_CAPABILITY.into(), JMAP_CONTACTS_CAPABILITY.into()],
159                JmapGetOptions {
160                    ids: opts.ids,
161                    properties: props,
162                },
163            )?),
164        })
165    }
166}
167
168impl JmapCoroutine for JmapAddressBookGet {
169    type Yield = JmapYield;
170    type Return = Result<JmapAddressBookGetOutput, JmapAddressBookGetError>;
171
172    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
173        match &mut self.state {
174            State::Get(get) => {
175                let JmapGetOutput {
176                    list,
177                    not_found,
178                    state,
179                    keep_alive,
180                } = jmap_try!(get, arg);
181                JmapCoroutineState::Complete(Ok(JmapAddressBookGetOutput {
182                    address_books: list,
183                    not_found,
184                    new_state: state,
185                    keep_alive,
186                }))
187            }
188        }
189    }
190}
191
192enum State {
193    Get(JmapGet<JmapAddressBook>),
194}