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}