Skip to main content

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}