io_webdav/rfc4791/item/read.rs
1//! `read-item` coroutine: GET a calendar item by id.
2//!
3//! Stays byte-oriented: returns raw iCalendar bytes plus the
4//! response's `ETag` so io-calendar 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//! rfc4791::item::read::ReadItem,
17//! rfc4918::WebdavAuth,
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//! ReadItem::new(&base_url, &auth, "io-webdav", "/dav/calendars/personal/", "event-1");
29//! let mut arg = None;
30//!
31//! let item = 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(item)) => break item,
41//! WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
42//! }
43//! };
44//!
45//! println!("{} bytes, etag {:?}", item.data.len(), item.etag);
46//! ```
47
48use alloc::{string::String, vec::Vec};
49
50use log::trace;
51use url::Url;
52
53use crate::{
54 coroutine::*,
55 rfc4791::item::join_path,
56 rfc4918::{
57 WebdavAuth,
58 get::Get,
59 read_etag,
60 send::{SendError, SendOk},
61 },
62 webdav_try,
63};
64
65/// Coroutine that reads a calendar item.
66#[derive(Debug)]
67pub struct ReadItem {
68 state: State,
69}
70
71impl ReadItem {
72 /// Builds a new `read-item` coroutine.
73 pub fn new(
74 base_url: &Url,
75 auth: &WebdavAuth,
76 user_agent: &str,
77 calendar_path: &str,
78 item_id: &str,
79 ) -> Self {
80 let path = join_path(calendar_path, item_id);
81 Self {
82 state: State::Get(Get::new(base_url, auth, user_agent, &path)),
83 }
84 }
85}
86
87impl WebdavCoroutine for ReadItem {
88 type Yield = WebdavYield;
89 type Return = Result<ItemBody, SendError>;
90
91 fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
92 trace!("sending request");
93 match &mut self.state {
94 State::Get(get) => {
95 let SendOk { response, body, .. } = webdav_try!(get, arg);
96 let etag = read_etag(&response);
97 WebdavCoroutineState::Complete(Ok(ItemBody { data: body, etag }))
98 }
99 }
100 }
101}
102
103#[derive(Debug)]
104enum State {
105 Get(Get),
106}
107
108/// Item body plus optional ETag returned by
109/// [`ReadItem`].
110#[derive(Clone, Debug)]
111pub struct ItemBody {
112 /// Raw iCalendar bytes.
113 pub data: Vec<u8>,
114 /// Entity tag (RFC 9110 ยง8.8.3), without surrounding quotes.
115 pub etag: Option<String>,
116}