Skip to main content

vcard/tree/param/
node.rs

1//! # Parameter node
2//!
3//! The raw, byte-faithful parameter on the syntax side.
4//!
5//! [`VcardParamNode`] is the syntactic peer of the decoded
6//! [`VcardParam`](crate::param::VcardParam): a name leaf and its raw value
7//! leaves, parsed from and serialized back to the wire verbatim. The per-name
8//! lens markers that give it meaning live alongside in [`crate::tree::param`].
9
10use core::fmt;
11
12use alloc::vec::Vec;
13
14use crate::tree::leaf::VcardLeaf;
15
16/// One raw parameter: a name and its `,`-separated raw values (empty when the
17/// parameter has no `=` list). The syntactic peer of the decoded
18/// [`VcardParam`](crate::param::VcardParam).
19#[derive(Clone, Debug)]
20pub struct VcardParamNode<'a> {
21    /// The parameter name leaf.
22    pub name: VcardLeaf<'a>,
23    /// The raw value leaves.
24    pub values: Vec<VcardLeaf<'a>>,
25}
26
27impl<'a> VcardParamNode<'a> {
28    /// Parse one `name=value,value` parameter (commas outside quotes split).
29    pub fn parse(param: &'a str) -> Self {
30        match param.split_once('=') {
31            Some((name, values)) => Self {
32                name: VcardLeaf::from(name),
33                values: split_param_values(values)
34                    .into_iter()
35                    .map(VcardLeaf::from)
36                    .collect(),
37            },
38            None => Self {
39                name: VcardLeaf::from(param),
40                values: Vec::new(),
41            },
42        }
43    }
44
45    /// Convert into an owned parameter node (`'static`).
46    pub(crate) fn into_static(self) -> VcardParamNode<'static> {
47        VcardParamNode {
48            name: self.name.into_static(),
49            values: self
50                .values
51                .into_iter()
52                .map(VcardLeaf::into_static)
53                .collect(),
54        }
55    }
56
57    /// Serialize the parameter (`name` or `name=value,value`) into `out`,
58    /// without the intermediate `String` a `Display`-based path would allocate.
59    pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
60        out.extend_from_slice(self.name.get().as_bytes());
61
62        if let Some((first, rest)) = self.values.split_first() {
63            out.push(b'=');
64            out.extend_from_slice(first.get().as_bytes());
65            for value in rest {
66                out.push(b',');
67                out.extend_from_slice(value.get().as_bytes());
68            }
69        }
70    }
71}
72
73impl fmt::Display for VcardParamNode<'_> {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        f.write_str(self.name.get())?;
76
77        if let Some((first, rest)) = self.values.split_first() {
78            write!(f, "={}", first.get())?;
79
80            for value in rest {
81                write!(f, ",{}", value.get())?;
82            }
83        }
84
85        Ok(())
86    }
87}
88
89/// Split a parameter value list on commas outside double quotes.
90fn split_param_values(values: &str) -> Vec<&str> {
91    let bytes = values.as_bytes();
92    let mut pieces = Vec::new();
93    let mut start = 0;
94    let mut quoted = false;
95
96    for (i, &byte) in bytes.iter().enumerate() {
97        match byte {
98            b'"' => quoted = !quoted,
99            b',' if !quoted => {
100                pieces.push(&values[start..i]);
101                start = i + 1;
102            }
103            _ => {}
104        }
105    }
106
107    pieces.push(&values[start..]);
108    pieces
109}
110
111#[cfg(test)]
112mod tests {
113    use alloc::string::ToString;
114
115    use crate::tree::param::node::VcardParamNode;
116
117    #[test]
118    fn parses_quoted_values_then_round_trips() {
119        let node = VcardParamNode::parse(r#"TYPE=work,"a,b""#);
120        assert_eq!(node.values.len(), 2);
121        assert_eq!(node.to_string(), r#"TYPE=work,"a,b""#);
122    }
123}