1pub mod adr;
21pub mod binary;
22pub mod client_pid_map;
23pub mod datetime;
24pub mod gender;
25pub mod geo;
26pub mod language;
27pub mod n;
28pub mod org;
29pub mod text;
30pub mod uri;
31pub mod utc_offset;
32
33use core::{error, fmt, ops, str};
34
35use alloc::{
36 borrow::Cow,
37 string::{String, ToString},
38 vec::Vec,
39};
40
41use crate::value::{
42 adr::VcardAdr,
43 binary::VcardBinary,
44 client_pid_map::VcardClientPidMap,
45 datetime::{VcardDateAndOrTime, VcardTimestamp},
46 gender::VcardGender,
47 geo::VcardGeo,
48 language::VcardLanguageTag,
49 n::VcardN,
50 org::VcardOrg,
51 text::{VcardText, VcardTextList},
52 uri::VcardUri,
53 utc_offset::VcardUtcOffset,
54};
55
56#[derive(Debug)]
58pub struct VcardValueKindParseError(
59 String,
61);
62
63impl fmt::Display for VcardValueKindParseError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "Cannot parse vCard value type `{}`", self.0)
66 }
67}
68
69impl error::Error for VcardValueKindParseError {}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum VcardValueKind {
77 Adr,
79 Binary,
81 ClientPidMap,
83 DateAndOrTime,
85 Gender,
87 Geo,
89 LanguageTag,
91 N,
93 Org,
95 Text,
97 TextList,
99 Timestamp,
101 Uri,
103 UtcOffset,
105}
106
107impl str::FromStr for VcardValueKind {
108 type Err = VcardValueKindParseError;
109
110 fn from_str(kind: &str) -> Result<Self, Self::Err> {
114 match kind {
115 kind if kind.eq_ignore_ascii_case("ADR") => Ok(Self::Adr),
116 kind if kind.eq_ignore_ascii_case("B") => Ok(Self::Binary),
117 kind if kind.eq_ignore_ascii_case("BINARY") => Ok(Self::Binary),
118 kind if kind.eq_ignore_ascii_case("CLIENTPIDMAP") => Ok(Self::ClientPidMap),
119 kind if kind.eq_ignore_ascii_case("DATE") => Ok(Self::DateAndOrTime),
120 kind if kind.eq_ignore_ascii_case("DATE-AND-OR-TIME") => Ok(Self::DateAndOrTime),
121 kind if kind.eq_ignore_ascii_case("DATE-TIME") => Ok(Self::DateAndOrTime),
122 kind if kind.eq_ignore_ascii_case("GENDER") => Ok(Self::Gender),
123 kind if kind.eq_ignore_ascii_case("GEO") => Ok(Self::Geo),
124 kind if kind.eq_ignore_ascii_case("LANGUAGE-TAG") => Ok(Self::LanguageTag),
125 kind if kind.eq_ignore_ascii_case("N") => Ok(Self::N),
126 kind if kind.eq_ignore_ascii_case("ORG") => Ok(Self::Org),
127 kind if kind.eq_ignore_ascii_case("TEXT") => Ok(Self::Text),
128 kind if kind.eq_ignore_ascii_case("TEXT-LIST") => Ok(Self::TextList),
129 kind if kind.eq_ignore_ascii_case("TIME") => Ok(Self::DateAndOrTime),
130 kind if kind.eq_ignore_ascii_case("TIMESTAMP") => Ok(Self::Timestamp),
131 kind if kind.eq_ignore_ascii_case("URI") => Ok(Self::Uri),
132 kind if kind.eq_ignore_ascii_case("URL") => Ok(Self::Uri),
133 kind if kind.eq_ignore_ascii_case("UTC-OFFSET") => Ok(Self::UtcOffset),
134 _ => Err(VcardValueKindParseError(kind.to_string())),
135 }
136 }
137}
138
139impl ops::Deref for VcardValueKind {
140 type Target = str;
141
142 fn deref(&self) -> &Self::Target {
143 match self {
144 Self::Adr => "ADR",
145 Self::Binary => "BINARY",
146 Self::ClientPidMap => "CLIENTPIDMAP",
147 Self::DateAndOrTime => "DATE-AND-OR-TIME",
148 Self::Gender => "GENDER",
149 Self::Geo => "GEO",
150 Self::LanguageTag => "LANGUAGE-TAG",
151 Self::N => "N",
152 Self::Org => "ORG",
153 Self::Text => "TEXT",
154 Self::TextList => "TEXT-LIST",
155 Self::Timestamp => "TIMESTAMP",
156 Self::Uri => "URI",
157 Self::UtcOffset => "UTC-OFFSET",
158 }
159 }
160}
161
162#[allow(clippy::large_enum_variant)]
167#[derive(Clone, Debug, PartialEq, Eq)]
168pub enum VcardValue<'a> {
169 Adr(VcardAdr<'a>),
171 Binary(VcardBinary<'a>),
174 ClientPidMap(VcardClientPidMap<'a>),
176 DateAndOrTime(VcardDateAndOrTime<'a>),
178 Gender(VcardGender<'a>),
180 Geo(VcardGeo<'a>),
182 LanguageTag(VcardLanguageTag<'a>),
184 N(VcardN<'a>),
186 Org(VcardOrg<'a>),
188 Text(VcardText<'a>),
190 TextList(VcardTextList<'a>),
192 Timestamp(VcardTimestamp<'a>),
194 Uri(VcardUri<'a>),
196 UtcOffset(VcardUtcOffset<'a>),
198 Unknown(VcardValueUnknown<'a>),
201}
202
203impl VcardValue<'_> {
204 pub fn kind(&self) -> Option<VcardValueKind> {
207 match self {
208 Self::Adr(_) => Some(VcardValueKind::Adr),
209 Self::Binary(_) => Some(VcardValueKind::Binary),
210 Self::ClientPidMap(_) => Some(VcardValueKind::ClientPidMap),
211 Self::DateAndOrTime(_) => Some(VcardValueKind::DateAndOrTime),
212 Self::Gender(_) => Some(VcardValueKind::Gender),
213 Self::Geo(_) => Some(VcardValueKind::Geo),
214 Self::LanguageTag(_) => Some(VcardValueKind::LanguageTag),
215 Self::N(_) => Some(VcardValueKind::N),
216 Self::Org(_) => Some(VcardValueKind::Org),
217 Self::Text(_) => Some(VcardValueKind::Text),
218 Self::TextList(_) => Some(VcardValueKind::TextList),
219 Self::Timestamp(_) => Some(VcardValueKind::Timestamp),
220 Self::Uri(_) => Some(VcardValueKind::Uri),
221 Self::UtcOffset(_) => Some(VcardValueKind::UtcOffset),
222 Self::Unknown(_) => None,
223 }
224 }
225
226 pub fn empty(kind: VcardValueKind) -> VcardValue<'static> {
234 match kind {
235 VcardValueKind::Adr => VcardValue::Adr(VcardAdr::default()),
236 VcardValueKind::Binary => VcardValue::Binary(VcardBinary::Uri(Cow::Borrowed(""))),
237 VcardValueKind::ClientPidMap => VcardValue::ClientPidMap(VcardClientPidMap::default()),
238 VcardValueKind::DateAndOrTime => {
239 VcardValue::DateAndOrTime(VcardDateAndOrTime::default())
240 }
241 VcardValueKind::Gender => VcardValue::Gender(VcardGender::default()),
242 VcardValueKind::Geo => VcardValue::Geo(VcardGeo::default()),
243 VcardValueKind::LanguageTag => VcardValue::LanguageTag(VcardLanguageTag::default()),
244 VcardValueKind::N => VcardValue::N(VcardN::default()),
245 VcardValueKind::Org => VcardValue::Org(VcardOrg::default()),
246 VcardValueKind::Text => VcardValue::Text(VcardText::default()),
247 VcardValueKind::TextList => VcardValue::TextList(VcardTextList::default()),
248 VcardValueKind::Timestamp => VcardValue::Timestamp(VcardTimestamp::default()),
249 VcardValueKind::Uri => VcardValue::Uri(VcardUri::default()),
250 VcardValueKind::UtcOffset => VcardValue::UtcOffset(VcardUtcOffset::default()),
251 }
252 }
253}
254
255#[derive(Clone, Debug, Default, PartialEq, Eq)]
258pub struct VcardValueUnknown<'a> {
259 pub components: Vec<Vec<Cow<'a, str>>>,
261}
262
263#[cfg(test)]
264mod tests {
265 use core::str::FromStr;
266
267 use crate::value::{VcardValue, VcardValueKind, VcardValueUnknown, text::VcardText};
268
269 #[test]
270 fn empty_is_the_inverse_of_kind() {
271 use crate::value::VcardValueKind::*;
272
273 for kind in [
274 Adr,
275 Binary,
276 ClientPidMap,
277 DateAndOrTime,
278 Gender,
279 Geo,
280 LanguageTag,
281 N,
282 Org,
283 Text,
284 TextList,
285 Timestamp,
286 Uri,
287 UtcOffset,
288 ] {
289 assert_eq!(VcardValue::empty(kind).kind(), Some(kind));
290 }
291 }
292
293 #[test]
294 fn reports_the_kind_of_a_value_and_none_for_unknown() {
295 assert_eq!(
296 VcardValue::Text(VcardText::default()).kind(),
297 Some(VcardValueKind::Text),
298 );
299 assert_eq!(
300 VcardValue::Unknown(VcardValueUnknown::default()).kind(),
301 None,
302 );
303 }
304
305 #[test]
306 fn maps_value_param_strings_liberally_and_case_insensitively() {
307 assert_eq!("URI".parse().ok(), Some(VcardValueKind::Uri));
308 assert_eq!("date".parse().ok(), Some(VcardValueKind::DateAndOrTime));
309 assert!(VcardValueKind::from_str("bogus").is_err());
310 }
311}