Skip to main content

io_webdav/rfc6352/card/
multiget.rs

1//! `multiget-cards` coroutine: REPORT `addressbook-multiget` against an
2//! addressbook collection (RFC 6352 §8.7).
3//!
4//! Fetches a batch of card bodies by resource name in a single
5//! round-trip, instead
6//! of one GET per card. Stays byte-oriented: the vCard payload is
7//! returned as raw bytes.
8//!
9//! # Example
10//!
11//! ```rust,no_run
12//! use std::{
13//!     io::{Read, Write},
14//!     net::TcpStream,
15//! };
16//!
17//! use io_webdav::{
18//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
19//!     rfc4918::WebdavAuth,
20//!     rfc6352::card::multiget::MultigetCards,
21//! };
22//! use url::Url;
23//!
24//! // Ready stream needed (TCP-connected, TLS-negociated)
25//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
26//! let mut buf = [0u8; 4096];
27//!
28//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
29//! let auth = WebdavAuth::None;
30//! let mut coroutine = MultigetCards::new(
31//!     &base_url,
32//!     &auth,
33//!     "io-webdav",
34//!     "/dav/addressbooks/contacts/",
35//!     &["alice", "bob"],
36//! );
37//! let mut arg = None;
38//!
39//! let cards = loop {
40//!     match coroutine.resume(arg.take()) {
41//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
42//!             stream.write_all(&bytes).unwrap();
43//!         }
44//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
45//!             let n = stream.read(&mut buf).unwrap();
46//!             arg = Some(&buf[..n]);
47//!         }
48//!         WebdavCoroutineState::Complete(Ok(cards)) => break cards,
49//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
50//!     }
51//! };
52//!
53//! println!("{} cards", cards.len());
54//! ```
55
56use alloc::{string::String, vec::Vec};
57
58use log::trace;
59use url::Url;
60
61use crate::{
62    coroutine::*,
63    rfc4918::{WebdavAuth, report::Report, send::SendError},
64    rfc6352::{
65        addressbook::addressbook_multiget_body,
66        card::{CARD_PROPS, CardEntry, card_from_entry, join_path},
67    },
68    webdav_try,
69};
70
71/// Coroutine that batch-fetches cards by resource name via REPORT
72/// `addressbook-multiget`.
73#[derive(Debug)]
74pub struct MultigetCards {
75    state: State,
76}
77
78impl MultigetCards {
79    /// Builds a new `multiget-cards` coroutine fetching each card of
80    /// `uris` (resource names as the server returned them) inside
81    /// `addressbook_path`. The `Depth` header is pinned to 0: RFC 6352
82    /// §8.7 only defines the report for that value.
83    pub fn new(
84        base_url: &Url,
85        auth: &WebdavAuth,
86        user_agent: &str,
87        addressbook_path: &str,
88        uris: &[&str],
89    ) -> Self {
90        let hrefs: Vec<String> = uris
91            .iter()
92            .map(|uri| join_path(addressbook_path, uri))
93            .collect();
94        let body = addressbook_multiget_body(&hrefs, CARD_PROPS);
95        let report = Report::new(base_url, auth, user_agent, addressbook_path, 0, body);
96        Self {
97            state: State::Report(report),
98        }
99    }
100}
101
102impl WebdavCoroutine for MultigetCards {
103    type Yield = WebdavYield;
104    type Return = Result<Vec<CardEntry>, SendError>;
105
106    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
107        trace!("sending request");
108        match &mut self.state {
109            State::Report(report) => {
110                let multistatus = webdav_try!(report, arg);
111                let cards = multistatus
112                    .responses
113                    .iter()
114                    .filter_map(card_from_entry)
115                    .collect();
116                WebdavCoroutineState::Complete(Ok(cards))
117            }
118        }
119    }
120}
121
122#[derive(Debug)]
123enum State {
124    Report(Report),
125}