Skip to main content

io_webdav/rfc6352/card/
update.rs

1//! `update-card` coroutine: PUT raw vCard bytes against an existing
2//! card.
3//!
4//! Supports the optional `If-Match` precondition so callers can gate
5//! the write on the last-known ETag (RFC 9110 ยง13.1.1).
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use std::{
11//!     io::{Read, Write},
12//!     net::TcpStream,
13//! };
14//!
15//! use io_webdav::{
16//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
17//!     rfc4918::WebdavAuth,
18//!     rfc6352::card::update::UpdateCard,
19//! };
20//! use url::Url;
21//!
22//! // Ready stream needed (TCP-connected, TLS-negociated)
23//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
24//! let mut buf = [0u8; 4096];
25//!
26//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
27//! let auth = WebdavAuth::None;
28//! let vcard = b"BEGIN:VCARD\r\n...\r\nEND:VCARD\r\n".to_vec();
29//! let mut coroutine = UpdateCard::new(
30//!     &base_url,
31//!     &auth,
32//!     "io-webdav",
33//!     "/dav/addressbooks/contacts/",
34//!     "alice",
35//!     vcard,
36//!     Some("\"abc123\""),
37//! );
38//! let mut arg = None;
39//!
40//! let updated = loop {
41//!     match coroutine.resume(arg.take()) {
42//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
43//!             stream.write_all(&bytes).unwrap();
44//!         }
45//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
46//!             let n = stream.read(&mut buf).unwrap();
47//!             arg = Some(&buf[..n]);
48//!         }
49//!         WebdavCoroutineState::Complete(Ok(updated)) => break updated,
50//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
51//!     }
52//! };
53//!
54//! println!("updated {} (etag {:?})", updated.uri, updated.etag);
55//! ```
56
57use core::mem;
58
59use alloc::{
60    string::{String, ToString},
61    vec::Vec,
62};
63
64use log::trace;
65use url::Url;
66
67use crate::{
68    coroutine::*,
69    rfc4918::{
70        WebdavAuth,
71        put::{Put, PutArgs},
72        read_etag,
73        send::{SendError, SendOk},
74    },
75    rfc6352::card::join_path,
76    webdav_try,
77};
78
79/// Coroutine that updates a card.
80#[derive(Debug)]
81pub struct UpdateCard {
82    uri: String,
83    state: State,
84}
85
86impl UpdateCard {
87    /// Builds a new `update-card` coroutine. `uri` is the resource name
88    /// as the server returned it (`CardEntry::uri`).
89    pub fn new(
90        base_url: &Url,
91        auth: &WebdavAuth,
92        user_agent: &str,
93        addressbook_path: &str,
94        uri: &str,
95        vcard: Vec<u8>,
96        if_match: Option<&str>,
97    ) -> Self {
98        let path = join_path(addressbook_path, uri);
99        let put = Put::new(PutArgs {
100            base_url,
101            auth,
102            user_agent,
103            path: &path,
104            content_type: "text/vcard; charset=utf-8",
105            body: vcard,
106            if_match,
107            if_none_match: None,
108        });
109        Self {
110            uri: uri.to_string(),
111            state: State::Put(put),
112        }
113    }
114}
115
116impl WebdavCoroutine for UpdateCard {
117    type Yield = WebdavYield;
118    type Return = Result<UpdateCardOk, SendError>;
119
120    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
121        trace!("sending request");
122        match &mut self.state {
123            State::Put(put) => {
124                let SendOk { response, .. } = webdav_try!(put, arg);
125                let etag = read_etag(&response);
126                let uri = mem::take(&mut self.uri);
127                WebdavCoroutineState::Complete(Ok(UpdateCardOk { uri, etag }))
128            }
129        }
130    }
131}
132
133#[derive(Debug)]
134enum State {
135    Put(Put),
136}
137
138/// Outcome of a successful
139/// [`UpdateCard`] resume.
140#[derive(Clone, Debug)]
141pub struct UpdateCardOk {
142    /// Card resource name (as supplied by the caller).
143    pub uri: String,
144    /// Updated entity tag returned by the server, when present.
145    pub etag: Option<String>,
146}