mod date_time;
mod language_tag;
mod name;
mod uri;
use std::{
fmt::{self, Display, Formatter, Write as _},
str::FromStr,
};
pub use date_time::{Date, DateAndOrTime, DateTime, Time, Timestamp, UtcOffset, Zone};
pub use language_tag::LanguageTag;
pub use name::{AddressValue, NameValue};
pub use uri::Uri;
use validators::prelude::*;
use crate::{
error::InvalidValueError,
syntax::{is_token, split_unescaped, unescape_text, write_escaped_text},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Token(String);
impl Token {
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn is_x_name(&self) -> bool {
let bytes = self.0.as_bytes();
bytes.len() > 2 && (bytes[0] == b'x' || bytes[0] == b'X') && bytes[1] == b'-'
}
}
impl FromStr for Token {
type Err = InvalidValueError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if is_token(s) { Ok(Self(s.to_string())) } else { Err(InvalidValueError::new("token")) }
}
}
impl Display for Token {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KindValue {
Individual,
Group,
Org,
Location,
Extension(Token),
}
impl Display for KindValue {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Individual => f.write_str("individual"),
Self::Group => f.write_str("group"),
Self::Org => f.write_str("org"),
Self::Location => f.write_str("location"),
Self::Extension(token) => Display::fmt(token, f),
}
}
}
impl FromStr for KindValue {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("individual") {
Ok(Self::Individual)
} else if s.eq_ignore_ascii_case("group") {
Ok(Self::Group)
} else if s.eq_ignore_ascii_case("org") {
Ok(Self::Org)
} else if s.eq_ignore_ascii_case("location") {
Ok(Self::Location)
} else {
Token::from_str(s).map(Self::Extension)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Sex {
Male,
Female,
Other,
NoneOrNotApplicable,
Unknown,
}
impl Display for Sex {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match self {
Self::Male => "M",
Self::Female => "F",
Self::Other => "O",
Self::NoneOrNotApplicable => "N",
Self::Unknown => "U",
})
}
}
impl FromStr for Sex {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"M" | "m" => Ok(Self::Male),
"F" | "f" => Ok(Self::Female),
"O" | "o" => Ok(Self::Other),
"N" | "n" => Ok(Self::NoneOrNotApplicable),
"U" | "u" => Ok(Self::Unknown),
_ => Err(InvalidValueError::new("sex")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct GenderValue {
pub sex: Option<Sex>,
pub identity: Option<String>,
}
impl Display for GenderValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Some(sex) = self.sex {
Display::fmt(&sex, f)?;
}
if let Some(identity) = &self.identity {
f.write_char(';')?;
write_escaped_text(f, identity, true)?;
}
Ok(())
}
}
impl FromStr for GenderValue {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split_unescaped(s, b';');
let sex = match parts[0] {
"" => None,
sex => Some(Sex::from_str(sex)?),
};
let identity = match parts.get(1) {
Some(identity) if !identity.is_empty() => Some(unescape_text(identity)),
_ => None,
};
Ok(Self {
sex,
identity,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GramGenderValue {
Animate,
Common,
Feminine,
Inanimate,
Masculine,
Neuter,
Extension(Token),
}
impl Display for GramGenderValue {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Animate => f.write_str("animate"),
Self::Common => f.write_str("common"),
Self::Feminine => f.write_str("feminine"),
Self::Inanimate => f.write_str("inanimate"),
Self::Masculine => f.write_str("masculine"),
Self::Neuter => f.write_str("neuter"),
Self::Extension(token) => Display::fmt(token, f),
}
}
}
impl FromStr for GramGenderValue {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("animate") {
Ok(Self::Animate)
} else if s.eq_ignore_ascii_case("common") {
Ok(Self::Common)
} else if s.eq_ignore_ascii_case("feminine") {
Ok(Self::Feminine)
} else if s.eq_ignore_ascii_case("inanimate") {
Ok(Self::Inanimate)
} else if s.eq_ignore_ascii_case("masculine") {
Ok(Self::Masculine)
} else if s.eq_ignore_ascii_case("neuter") {
Ok(Self::Neuter)
} else {
Token::from_str(s).map(Self::Extension)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct OrgValue {
pub name: String,
pub units: Vec<String>,
}
impl Display for OrgValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write_escaped_text(f, &self.name, true)?;
for unit in &self.units {
f.write_char(';')?;
write_escaped_text(f, unit, true)?;
}
Ok(())
}
}
impl FromStr for OrgValue {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split_unescaped(s, b';');
Ok(Self {
name: unescape_text(parts[0]),
units: parts[1..].iter().map(|unit| unescape_text(unit)).collect(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientPidMapValue {
pub source_id: u32,
pub uri: Uri,
}
impl Display for ClientPidMapValue {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{};{}", self.source_id, self.uri)
}
}
impl FromStr for ClientPidMapValue {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
const ERROR: InvalidValueError = InvalidValueError::new("client pid map");
let (source_id, uri) = s.split_once(';').ok_or(ERROR)?;
Ok(Self {
source_id: source_id.parse().map_err(|_| ERROR)?,
uri: Uri::from_str(uri).map_err(|_| ERROR)?,
})
}
}
#[derive(Validator)]
#[validator(email(
comment(Disallow),
ip(Allow),
local(Allow),
at_least_two_labels(Allow),
non_ascii(Allow)
))]
#[allow(dead_code)]
struct EmailValidator {
local_part: String,
need_quoted: bool,
domain_part: validators::models::Host,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EmailValue(String);
impl EmailValue {
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for EmailValue {
type Err = InvalidValueError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
EmailValidator::validate_str(s).map_err(|_| InvalidValueError::new("email address"))?;
Ok(Self(s.to_string()))
}
}
impl Display for EmailValue {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Validator)]
#[validator(phone)]
#[allow(dead_code)]
struct PhoneValidator(validators::phonenumber::PhoneNumber);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TelValue {
Uri(Uri),
Text(String),
}
impl TelValue {
#[inline]
pub fn from_phone_number_str(s: &str) -> Result<Self, InvalidValueError> {
PhoneValidator::validate_str(s).map_err(|_| InvalidValueError::new("phone number"))?;
Ok(Self::Text(s.to_string()))
}
}
#[derive(Validator)]
#[validator(uuid(case(Any), separator(Allow(b'-'))))]
#[allow(dead_code)]
struct UuidValidator(u128);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TextOrUri {
Uri(Uri),
Text(String),
}
impl TextOrUri {
pub fn from_uuid_str(s: &str) -> Result<Self, InvalidValueError> {
let uuid = UuidValidator::parse_str(s).map_err(|_| InvalidValueError::new("uuid"))?.0;
let uri = format!(
"urn:uuid:{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
(uuid >> 96) as u32,
(uuid >> 80) as u16,
(uuid >> 64) as u16,
(uuid >> 48) as u16,
(uuid & 0xFFFF_FFFF_FFFF) as u64
);
Ok(Self::Uri(Uri::from_str(&uri).unwrap()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TzValue {
Text(String),
Uri(Uri),
UtcOffset(UtcOffset),
}
impl TzValue {
#[inline]
pub fn from_time_zone(tz: chrono_tz::Tz) -> Self {
Self::Text(tz.name().to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DateAndOrTimeOrText {
DateAndOrTime(DateAndOrTime),
Text(String),
}
impl From<DateAndOrTime> for DateAndOrTimeOrText {
#[inline]
fn from(value: DateAndOrTime) -> Self {
Self::DateAndOrTime(value)
}
}
impl From<Date> for DateAndOrTimeOrText {
#[inline]
fn from(value: Date) -> Self {
Self::DateAndOrTime(DateAndOrTime::Date(value))
}
}