use core::{error, fmt, ops, str};
use alloc::{
borrow::Cow,
string::{String, ToString},
vec::Vec,
};
#[derive(Debug)]
pub struct ParseVcardParamKindError(
String,
);
impl fmt::Display for ParseVcardParamKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Cannot parse vCard parameter `{}`", self.0)
}
}
impl error::Error for ParseVcardParamKindError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VcardParamKind {
AltId,
Author,
AuthorName,
CalScale,
Charset,
Created,
Derived,
Encoding,
Geo,
Jsptr,
Label,
Language,
MediaType,
Phonetic,
Pid,
Pref,
PropId,
Script,
ServiceType,
SortAs,
Type,
Tz,
Username,
Value,
}
impl str::FromStr for VcardParamKind {
type Err = ParseVcardParamKindError;
fn from_str(kind: &str) -> Result<Self, Self::Err> {
let kind = match kind {
kind if kind.eq_ignore_ascii_case("ALTID") => Self::AltId,
kind if kind.eq_ignore_ascii_case("AUTHOR") => Self::Author,
kind if kind.eq_ignore_ascii_case("AUTHOR-NAME") => Self::AuthorName,
kind if kind.eq_ignore_ascii_case("CALSCALE") => Self::CalScale,
kind if kind.eq_ignore_ascii_case("CHARSET") => Self::Charset,
kind if kind.eq_ignore_ascii_case("CREATED") => Self::Created,
kind if kind.eq_ignore_ascii_case("DERIVED") => Self::Derived,
kind if kind.eq_ignore_ascii_case("ENCODING") => Self::Encoding,
kind if kind.eq_ignore_ascii_case("GEO") => Self::Geo,
kind if kind.eq_ignore_ascii_case("JSPTR") => Self::Jsptr,
kind if kind.eq_ignore_ascii_case("LABEL") => Self::Label,
kind if kind.eq_ignore_ascii_case("LANGUAGE") => Self::Language,
kind if kind.eq_ignore_ascii_case("MEDIATYPE") => Self::MediaType,
kind if kind.eq_ignore_ascii_case("PHONETIC") => Self::Phonetic,
kind if kind.eq_ignore_ascii_case("PID") => Self::Pid,
kind if kind.eq_ignore_ascii_case("PREF") => Self::Pref,
kind if kind.eq_ignore_ascii_case("PROP-ID") => Self::PropId,
kind if kind.eq_ignore_ascii_case("SCRIPT") => Self::Script,
kind if kind.eq_ignore_ascii_case("SERVICE-TYPE") => Self::ServiceType,
kind if kind.eq_ignore_ascii_case("SORT-AS") => Self::SortAs,
kind if kind.eq_ignore_ascii_case("TYPE") => Self::Type,
kind if kind.eq_ignore_ascii_case("TZ") => Self::Tz,
kind if kind.eq_ignore_ascii_case("USERNAME") => Self::Username,
kind if kind.eq_ignore_ascii_case("VALUE") => Self::Value,
_ => return Err(ParseVcardParamKindError(kind.to_string())),
};
Ok(kind)
}
}
impl ops::Deref for VcardParamKind {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::AltId => "ALTID",
Self::Author => "AUTHOR",
Self::AuthorName => "AUTHOR-NAME",
Self::CalScale => "CALSCALE",
Self::Charset => "CHARSET",
Self::Created => "CREATED",
Self::Derived => "DERIVED",
Self::Encoding => "ENCODING",
Self::Geo => "GEO",
Self::Jsptr => "JSPTR",
Self::Label => "LABEL",
Self::Language => "LANGUAGE",
Self::MediaType => "MEDIATYPE",
Self::Phonetic => "PHONETIC",
Self::Pid => "PID",
Self::Pref => "PREF",
Self::PropId => "PROP-ID",
Self::Script => "SCRIPT",
Self::ServiceType => "SERVICE-TYPE",
Self::SortAs => "SORT-AS",
Self::Type => "TYPE",
Self::Tz => "TZ",
Self::Username => "USERNAME",
Self::Value => "VALUE",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VcardParam<'a> {
AltId(Cow<'a, str>),
Author(Cow<'a, str>),
AuthorName(Cow<'a, str>),
CalScale(Cow<'a, str>),
Charset(Cow<'a, str>),
Created(Cow<'a, str>),
Derived(Cow<'a, str>),
Encoding(Cow<'a, str>),
Geo(Cow<'a, str>),
Jsptr(Cow<'a, str>),
Label(Cow<'a, str>),
Language(Cow<'a, str>),
MediaType(Cow<'a, str>),
Phonetic(Cow<'a, str>),
Pid(Vec<Cow<'a, str>>),
Pref(Cow<'a, str>),
PropId(Cow<'a, str>),
Script(Cow<'a, str>),
ServiceType(Cow<'a, str>),
SortAs(Vec<Cow<'a, str>>),
Type(Vec<Cow<'a, str>>),
Tz(Cow<'a, str>),
Username(Cow<'a, str>),
Value(Cow<'a, str>),
Unknown {
name: Cow<'a, str>,
values: Vec<Cow<'a, str>>,
},
}
impl VcardParam<'_> {
pub fn kind(&self) -> Option<VcardParamKind> {
match self {
Self::AltId(_) => Some(VcardParamKind::AltId),
Self::Author(_) => Some(VcardParamKind::Author),
Self::AuthorName(_) => Some(VcardParamKind::AuthorName),
Self::CalScale(_) => Some(VcardParamKind::CalScale),
Self::Charset(_) => Some(VcardParamKind::Charset),
Self::Created(_) => Some(VcardParamKind::Created),
Self::Derived(_) => Some(VcardParamKind::Derived),
Self::Encoding(_) => Some(VcardParamKind::Encoding),
Self::Geo(_) => Some(VcardParamKind::Geo),
Self::Jsptr(_) => Some(VcardParamKind::Jsptr),
Self::Label(_) => Some(VcardParamKind::Label),
Self::Language(_) => Some(VcardParamKind::Language),
Self::MediaType(_) => Some(VcardParamKind::MediaType),
Self::Phonetic(_) => Some(VcardParamKind::Phonetic),
Self::Pid(_) => Some(VcardParamKind::Pid),
Self::Pref(_) => Some(VcardParamKind::Pref),
Self::PropId(_) => Some(VcardParamKind::PropId),
Self::Script(_) => Some(VcardParamKind::Script),
Self::ServiceType(_) => Some(VcardParamKind::ServiceType),
Self::SortAs(_) => Some(VcardParamKind::SortAs),
Self::Type(_) => Some(VcardParamKind::Type),
Self::Tz(_) => Some(VcardParamKind::Tz),
Self::Username(_) => Some(VcardParamKind::Username),
Self::Value(_) => Some(VcardParamKind::Value),
Self::Unknown { .. } => None,
}
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use crate::param::VcardParamKind;
#[test]
fn round_trips_every_kind_through_its_wire_name() {
for kind in [
VcardParamKind::Type,
VcardParamKind::SortAs,
VcardParamKind::MediaType,
] {
assert_eq!(VcardParamKind::from_str(&kind).ok(), Some(kind));
}
assert_eq!(
VcardParamKind::from_str("type").ok(),
Some(VcardParamKind::Type),
);
assert!(VcardParamKind::from_str("X-CUSTOM").is_err());
}
}