Skip to main content

vcard/
param.rs

1//! # Parameters
2//!
3//! A decoded parameter and the RFC 6350 parameter-name vocabulary.
4//!
5//! [`VcardParam`] is a closed set of the parameters the RFCs define, one
6//! variant each, plus an [`Unknown`](VcardParam::Unknown) arm so anything else
7//! round-trips. Parameters are few and simple (a text, a list, a small
8//! integer), so unlike properties each variant carries its value directly
9//! rather than through a shared value type; the variant itself names the
10//! parameter. A known name is the closed [`VcardParamKind`], reached through
11//! `FromStr` and `Deref`. Pure model, no [`crate::tree`] dependency.
12
13use core::{error, fmt, ops, str};
14
15use alloc::{
16    borrow::Cow,
17    string::{String, ToString},
18    vec::Vec,
19};
20
21/// Parse vCard parameter kind error.
22#[derive(Debug)]
23pub struct VcardParamKindParseError(
24    /// The vCard parameter that cannot be parsed.
25    String,
26);
27
28impl fmt::Display for VcardParamKindParseError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "Cannot parse vCard parameter `{}`", self.0)
31    }
32}
33
34impl error::Error for VcardParamKindParseError {}
35
36/// The closed RFC 6350 parameter-name vocabulary, one fieldless variant per
37/// known parameter. An identity for dispatch and allowed-sets; the open
38/// counterpart that carries the value (and unknown names) is [`VcardParam`].
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum VcardParamKind {
41    /// `ALTID`: ties alternative representations together (RFC 6350 5.4).
42    AltId,
43    /// `AUTHOR`: URI of the author of the value (RFC 9554).
44    Author,
45    /// `AUTHOR-NAME`: name of the author of the value (RFC 9554).
46    AuthorName,
47    /// `CALSCALE`: calendar scale of a date/time value (RFC 6350 5.8).
48    CalScale,
49    /// `CHARSET`: character set of the value (vCard 2.1).
50    Charset,
51    /// `CREATED`: timestamp of the property's creation (RFC 9554).
52    Created,
53    /// `DERIVED`: whether the value derives from other properties (RFC 9554).
54    Derived,
55    /// `ENCODING`: inline encoding of the value (vCard 2.1 / 3.0).
56    Encoding,
57    /// `GEO`: global position of the property (RFC 6350 5.10).
58    Geo,
59    /// `JSPTR`: JSON pointer locating a preserved JSContact property (RFC
60    /// 9555).
61    Jsptr,
62    /// `LABEL`: formatted delivery-address label (RFC 6350 6.3.1).
63    Label,
64    /// `LANGUAGE`: language of the value (RFC 6350 5.1).
65    Language,
66    /// `MEDIATYPE`: media type of the referenced resource (RFC 6350 5.7).
67    MediaType,
68    /// `PHONETIC`: phonetic system the value is written in (RFC 9554).
69    Phonetic,
70    /// `PID`: source identifiers of the property instance (RFC 6350 5.5).
71    Pid,
72    /// `PREF`: preference among a set of instances (RFC 6350 5.3).
73    Pref,
74    /// `PROP-ID`: identity of the property instance across conversions (RFC
75    /// 9554).
76    PropId,
77    /// `SCRIPT`: script the value is written in (RFC 9554).
78    Script,
79    /// `SERVICE-TYPE`: online service the property points at (RFC 9554).
80    ServiceType,
81    /// `SORT-AS`: components to sort the property by (RFC 6350 5.9).
82    SortAs,
83    /// `TYPE`: kinds or contexts of the property (RFC 6350 5.6).
84    Type,
85    /// `TZ`: time zone of the property (RFC 6350 5.11).
86    Tz,
87    /// `USERNAME`: username on the online service (RFC 9554).
88    Username,
89    /// `VALUE`: value type the value is to be read as (RFC 6350 5.2).
90    Value,
91}
92
93impl str::FromStr for VcardParamKind {
94    type Err = VcardParamKindParseError;
95
96    /// The known parameter for a wire name (case-insensitive).
97    fn from_str(kind: &str) -> Result<Self, Self::Err> {
98        let kind = match kind {
99            kind if kind.eq_ignore_ascii_case("ALTID") => Self::AltId,
100            kind if kind.eq_ignore_ascii_case("AUTHOR") => Self::Author,
101            kind if kind.eq_ignore_ascii_case("AUTHOR-NAME") => Self::AuthorName,
102            kind if kind.eq_ignore_ascii_case("CALSCALE") => Self::CalScale,
103            kind if kind.eq_ignore_ascii_case("CHARSET") => Self::Charset,
104            kind if kind.eq_ignore_ascii_case("CREATED") => Self::Created,
105            kind if kind.eq_ignore_ascii_case("DERIVED") => Self::Derived,
106            kind if kind.eq_ignore_ascii_case("ENCODING") => Self::Encoding,
107            kind if kind.eq_ignore_ascii_case("GEO") => Self::Geo,
108            kind if kind.eq_ignore_ascii_case("JSPTR") => Self::Jsptr,
109            kind if kind.eq_ignore_ascii_case("LABEL") => Self::Label,
110            kind if kind.eq_ignore_ascii_case("LANGUAGE") => Self::Language,
111            kind if kind.eq_ignore_ascii_case("MEDIATYPE") => Self::MediaType,
112            kind if kind.eq_ignore_ascii_case("PHONETIC") => Self::Phonetic,
113            kind if kind.eq_ignore_ascii_case("PID") => Self::Pid,
114            kind if kind.eq_ignore_ascii_case("PREF") => Self::Pref,
115            kind if kind.eq_ignore_ascii_case("PROP-ID") => Self::PropId,
116            kind if kind.eq_ignore_ascii_case("SCRIPT") => Self::Script,
117            kind if kind.eq_ignore_ascii_case("SERVICE-TYPE") => Self::ServiceType,
118            kind if kind.eq_ignore_ascii_case("SORT-AS") => Self::SortAs,
119            kind if kind.eq_ignore_ascii_case("TYPE") => Self::Type,
120            kind if kind.eq_ignore_ascii_case("TZ") => Self::Tz,
121            kind if kind.eq_ignore_ascii_case("USERNAME") => Self::Username,
122            kind if kind.eq_ignore_ascii_case("VALUE") => Self::Value,
123            _ => return Err(VcardParamKindParseError(kind.to_string())),
124        };
125
126        Ok(kind)
127    }
128}
129
130impl ops::Deref for VcardParamKind {
131    type Target = str;
132
133    fn deref(&self) -> &Self::Target {
134        match self {
135            Self::AltId => "ALTID",
136            Self::Author => "AUTHOR",
137            Self::AuthorName => "AUTHOR-NAME",
138            Self::CalScale => "CALSCALE",
139            Self::Charset => "CHARSET",
140            Self::Created => "CREATED",
141            Self::Derived => "DERIVED",
142            Self::Encoding => "ENCODING",
143            Self::Geo => "GEO",
144            Self::Jsptr => "JSPTR",
145            Self::Label => "LABEL",
146            Self::Language => "LANGUAGE",
147            Self::MediaType => "MEDIATYPE",
148            Self::Phonetic => "PHONETIC",
149            Self::Pid => "PID",
150            Self::Pref => "PREF",
151            Self::PropId => "PROP-ID",
152            Self::Script => "SCRIPT",
153            Self::ServiceType => "SERVICE-TYPE",
154            Self::SortAs => "SORT-AS",
155            Self::Type => "TYPE",
156            Self::Tz => "TZ",
157            Self::Username => "USERNAME",
158            Self::Value => "VALUE",
159        }
160    }
161}
162
163/// A decoded parameter: one known kind, or `Unknown` for anything unmodelled.
164#[derive(Clone, Debug, PartialEq, Eq)]
165pub enum VcardParam<'a> {
166    /// `ALTID`: ties alternative representations of the same logical property.
167    AltId(Cow<'a, str>),
168    /// `AUTHOR`: the URI of the author of the value.
169    Author(Cow<'a, str>),
170    /// `AUTHOR-NAME`: the name of the author of the value.
171    AuthorName(Cow<'a, str>),
172    /// `CALSCALE`: the calendar scale of a date/time value.
173    CalScale(Cow<'a, str>),
174    /// `CHARSET`: the character set of the value (vCard 2.1).
175    Charset(Cow<'a, str>),
176    /// `CREATED`: the timestamp of the property's creation.
177    Created(Cow<'a, str>),
178    /// `DERIVED`: whether the value derives from other properties.
179    Derived(Cow<'a, str>),
180    /// `ENCODING`: the inline encoding of the value (vCard 2.1 / 3.0).
181    Encoding(Cow<'a, str>),
182    /// `GEO`: a global positioning value for the property.
183    Geo(Cow<'a, str>),
184    /// `JSPTR`: the JSON pointer locating a preserved JSContact property.
185    Jsptr(Cow<'a, str>),
186    /// `LABEL`: the formatted text of a delivery address.
187    Label(Cow<'a, str>),
188    /// `LANGUAGE`: the language of the property value (RFC 5646 tag).
189    Language(Cow<'a, str>),
190    /// `MEDIATYPE`: the media type of the referenced resource.
191    MediaType(Cow<'a, str>),
192    /// `PHONETIC`: the phonetic system the value is written in.
193    Phonetic(Cow<'a, str>),
194    /// `PID`: the source identifiers of this property instance.
195    Pid(Vec<Cow<'a, str>>),
196    /// `PREF`: the preference of this instance among a set (1-100).
197    Pref(Cow<'a, str>),
198    /// `PROP-ID`: the identity of this property instance across conversions.
199    PropId(Cow<'a, str>),
200    /// `SCRIPT`: the script the value is written in.
201    Script(Cow<'a, str>),
202    /// `SERVICE-TYPE`: the online service the property points at.
203    ServiceType(Cow<'a, str>),
204    /// `SORT-AS`: the components to sort the property by.
205    SortAs(Vec<Cow<'a, str>>),
206    /// `TYPE`: the kinds or contexts of the property (e.g. `work`, `home`).
207    Type(Vec<Cow<'a, str>>),
208    /// `TZ`: the time zone of the property.
209    Tz(Cow<'a, str>),
210    /// `USERNAME`: the username on the online service.
211    Username(Cow<'a, str>),
212    /// `VALUE`: the value type the property value is to be read as.
213    Value(Cow<'a, str>),
214    /// Any parameter the model does not decode: its name and its values.
215    Unknown {
216        /// The verbatim parameter name, as it was spelled on the wire.
217        name: Cow<'a, str>,
218        /// The `,`-separated raw values, empty when the parameter carries none.
219        values: Vec<Cow<'a, str>>,
220    },
221}
222
223impl VcardParam<'_> {
224    /// The closed [`VcardParamKind`] of this parameter, or `None` for
225    /// [`Unknown`](VcardParam::Unknown) (which is outside the vocabulary).
226    pub fn kind(&self) -> Option<VcardParamKind> {
227        match self {
228            Self::AltId(_) => Some(VcardParamKind::AltId),
229            Self::Author(_) => Some(VcardParamKind::Author),
230            Self::AuthorName(_) => Some(VcardParamKind::AuthorName),
231            Self::CalScale(_) => Some(VcardParamKind::CalScale),
232            Self::Charset(_) => Some(VcardParamKind::Charset),
233            Self::Created(_) => Some(VcardParamKind::Created),
234            Self::Derived(_) => Some(VcardParamKind::Derived),
235            Self::Encoding(_) => Some(VcardParamKind::Encoding),
236            Self::Geo(_) => Some(VcardParamKind::Geo),
237            Self::Jsptr(_) => Some(VcardParamKind::Jsptr),
238            Self::Label(_) => Some(VcardParamKind::Label),
239            Self::Language(_) => Some(VcardParamKind::Language),
240            Self::MediaType(_) => Some(VcardParamKind::MediaType),
241            Self::Phonetic(_) => Some(VcardParamKind::Phonetic),
242            Self::Pid(_) => Some(VcardParamKind::Pid),
243            Self::Pref(_) => Some(VcardParamKind::Pref),
244            Self::PropId(_) => Some(VcardParamKind::PropId),
245            Self::Script(_) => Some(VcardParamKind::Script),
246            Self::ServiceType(_) => Some(VcardParamKind::ServiceType),
247            Self::SortAs(_) => Some(VcardParamKind::SortAs),
248            Self::Type(_) => Some(VcardParamKind::Type),
249            Self::Tz(_) => Some(VcardParamKind::Tz),
250            Self::Username(_) => Some(VcardParamKind::Username),
251            Self::Value(_) => Some(VcardParamKind::Value),
252            Self::Unknown { .. } => None,
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use core::str::FromStr;
260
261    use crate::param::VcardParamKind;
262
263    #[test]
264    fn round_trips_every_kind_through_its_wire_name() {
265        for kind in [
266            VcardParamKind::Type,
267            VcardParamKind::SortAs,
268            VcardParamKind::MediaType,
269        ] {
270            assert_eq!(VcardParamKind::from_str(&kind).ok(), Some(kind));
271        }
272        // NOTE: Case-insensitive on the way in; unknown names are not in the
273        // vocabulary.
274        assert_eq!(
275            VcardParamKind::from_str("type").ok(),
276            Some(VcardParamKind::Type),
277        );
278        assert!(VcardParamKind::from_str("X-CUSTOM").is_err());
279    }
280}