io_webdav/rfc6352/card/read.rs
1//! `read-card` coroutine: GET a card by its resource name.
2//!
3//! Stays byte-oriented: returns raw vCard bytes plus the response's
4//! ETag so io-addressbook can run calcard upstream.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use std::{
10//! io::{Read, Write},
11//! net::TcpStream,
12//! };
13//!
14//! use io_webdav::{
15//! coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
16//! rfc4918::WebdavAuth,
17//! rfc6352::card::read::ReadCard,
18//! };
19//! use url::Url;
20//!
21//! // Ready stream needed (TCP-connected, TLS-negociated)
22//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
23//! let mut buf = [0u8; 4096];
24//!
25//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
26//! let auth = WebdavAuth::None;
27//! let mut coroutine =
28//! ReadCard::new(&base_url, &auth, "io-webdav", "/dav/addressbooks/contacts/", "alice");
29//! let mut arg = None;
30//!
31//! let card = loop {
32//! match coroutine.resume(arg.take()) {
33//! WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
34//! stream.write_all(&bytes).unwrap();
35//! }
36//! WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
37//! let n = stream.read(&mut buf).unwrap();
38//! arg = Some(&buf[..n]);
39//! }
40//! WebdavCoroutineState::Complete(Ok(card)) => break card,
41//! WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
42//! }
43//! };
44//!
45//! println!("{} bytes, etag {:?}", card.data.len(), card.etag);
46//! ```
47
48use alloc::{string::String, vec::Vec};
49
50use log::trace;
51use url::Url;
52
53use crate::{
54 coroutine::*,
55 rfc4918::{
56 WebdavAuth,
57 get::Get,
58 read_etag,
59 send::{SendError, SendOk},
60 },
61 rfc6352::card::join_path,
62 webdav_try,
63};
64
65/// Coroutine that reads a card.
66#[derive(Debug)]
67pub struct ReadCard {
68 state: State,
69}
70
71impl ReadCard {
72 /// Builds a new `read-card` coroutine. `card_uri` is the resource
73 /// name as the server returned it (`CardEntry::uri`).
74 pub fn new(
75 base_url: &Url,
76 auth: &WebdavAuth,
77 user_agent: &str,
78 addressbook_path: &str,
79 card_uri: &str,
80 ) -> Self {
81 let path = join_path(addressbook_path, card_uri);
82 Self {
83 state: State::Get(Get::new(base_url, auth, user_agent, &path)),
84 }
85 }
86}
87
88impl WebdavCoroutine for ReadCard {
89 type Yield = WebdavYield;
90 type Return = Result<CardBody, SendError>;
91
92 fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
93 trace!("sending request");
94 match &mut self.state {
95 State::Get(get) => {
96 let SendOk { response, body, .. } = webdav_try!(get, arg);
97 let etag = read_etag(&response);
98 WebdavCoroutineState::Complete(Ok(CardBody { data: body, etag }))
99 }
100 }
101 }
102}
103
104#[derive(Debug)]
105enum State {
106 Get(Get),
107}
108
109/// Card body plus optional ETag returned by
110/// [`ReadCard`].
111#[derive(Clone, Debug)]
112pub struct CardBody {
113 /// Raw vCard bytes.
114 pub data: Vec<u8>,
115 /// Entity tag (RFC 9110 ยง8.8.3), without surrounding quotes.
116 pub etag: Option<String>,
117}