use crate::{
param::{COMMON_PARAMS, IcalParamKind},
prop::{
IcalPropKind, aalarm, acknowledged, action, attach, attendee, busytype, calendar_address,
calscale, cardinality::IcalPropCardinality, categories, class, color, comment, completed,
concept, conference, contact, created, dalarm, description, dtend, dtstamp, dtstart, due,
duration, exdate, exrule, freebusy, geo, image, last_modified, link, location,
location_type, malarm, method, name, organizer, palarm, participant_type, percent_complete,
priority, prodid, proximity, rdate, recurrence_id, refid, refresh_interval, related_to,
repeat, request_status, resource_type, resources, rnum, rrule, sequence, source, status,
structured_data, styled_description, summary, transp, trigger, tz, tzid, tzname,
tzoffsetfrom, tzoffsetto, tzurl, uid, url,
},
value::IcalValueKind,
version::IcalVersion,
};
pub trait IcalPropSpec {
const KIND: IcalPropKind;
fn allowed_versions() -> &'static [IcalVersion] {
&[IcalVersion::V1_0, IcalVersion::V2_0]
}
fn cardinality(_version: IcalVersion) -> IcalPropCardinality {
IcalPropCardinality::Any
}
fn allowed_values(_version: IcalVersion) -> &'static [IcalValueKind] {
&[IcalValueKind::Text]
}
fn allowed_params(_version: IcalVersion) -> &'static [IcalParamKind] {
COMMON_PARAMS
}
fn value(version: IcalVersion, declared: Option<IcalValueKind>) -> IcalValueKind {
declared
.or_else(|| Self::allowed_values(version).first().copied())
.unwrap_or(IcalValueKind::Text)
}
}
#[allow(dead_code)]
pub(crate) struct IcalPropSpecFns {
pub kind: IcalPropKind,
pub allowed_versions: fn() -> &'static [IcalVersion],
pub cardinality: fn(IcalVersion) -> IcalPropCardinality,
pub allowed_values: fn(IcalVersion) -> &'static [IcalValueKind],
pub allowed_params: fn(IcalVersion) -> &'static [IcalParamKind],
pub value: fn(IcalVersion, Option<IcalValueKind>) -> IcalValueKind,
}
fn spec_fns<L: IcalPropSpec>() -> IcalPropSpecFns {
IcalPropSpecFns {
kind: L::KIND,
allowed_versions: L::allowed_versions,
cardinality: L::cardinality,
allowed_values: L::allowed_values,
allowed_params: L::allowed_params,
value: L::value,
}
}
pub(crate) fn prop_spec(prop: IcalPropKind) -> IcalPropSpecFns {
use IcalPropKind::*;
match prop {
CalScale => spec_fns::<calscale::CALSCALE>(),
Method => spec_fns::<method::METHOD>(),
ProdId => spec_fns::<prodid::PRODID>(),
Attach => spec_fns::<attach::ATTACH>(),
Categories => spec_fns::<categories::CATEGORIES>(),
Class => spec_fns::<class::CLASS>(),
Comment => spec_fns::<comment::COMMENT>(),
Description => spec_fns::<description::DESCRIPTION>(),
Geo => spec_fns::<geo::GEO>(),
Location => spec_fns::<location::LOCATION>(),
PercentComplete => spec_fns::<percent_complete::PERCENT_COMPLETE>(),
Priority => spec_fns::<priority::PRIORITY>(),
Resources => spec_fns::<resources::RESOURCES>(),
Status => spec_fns::<status::STATUS>(),
Summary => spec_fns::<summary::SUMMARY>(),
Completed => spec_fns::<completed::COMPLETED>(),
DtEnd => spec_fns::<dtend::DTEND>(),
Due => spec_fns::<due::DUE>(),
DtStart => spec_fns::<dtstart::DTSTART>(),
Duration => spec_fns::<duration::DURATION>(),
FreeBusy => spec_fns::<freebusy::FREEBUSY>(),
Transp => spec_fns::<transp::TRANSP>(),
TzId => spec_fns::<tzid::TZID>(),
TzName => spec_fns::<tzname::TZNAME>(),
TzOffsetFrom => spec_fns::<tzoffsetfrom::TZOFFSETFROM>(),
TzOffsetTo => spec_fns::<tzoffsetto::TZOFFSETTO>(),
TzUrl => spec_fns::<tzurl::TZURL>(),
Attendee => spec_fns::<attendee::ATTENDEE>(),
Contact => spec_fns::<contact::CONTACT>(),
Organizer => spec_fns::<organizer::ORGANIZER>(),
RecurrenceId => spec_fns::<recurrence_id::RECURRENCE_ID>(),
RelatedTo => spec_fns::<related_to::RELATED_TO>(),
Url => spec_fns::<url::URL>(),
Uid => spec_fns::<uid::UID>(),
ExDate => spec_fns::<exdate::EXDATE>(),
RDate => spec_fns::<rdate::RDATE>(),
RRule => spec_fns::<rrule::RRULE>(),
ExRule => spec_fns::<exrule::EXRULE>(),
Action => spec_fns::<action::ACTION>(),
Repeat => spec_fns::<repeat::REPEAT>(),
Trigger => spec_fns::<trigger::TRIGGER>(),
Created => spec_fns::<created::CREATED>(),
DtStamp => spec_fns::<dtstamp::DTSTAMP>(),
LastModified => spec_fns::<last_modified::LAST_MODIFIED>(),
Sequence => spec_fns::<sequence::SEQUENCE>(),
RequestStatus => spec_fns::<request_status::REQUEST_STATUS>(),
Name => spec_fns::<name::NAME>(),
RefreshInterval => spec_fns::<refresh_interval::REFRESH_INTERVAL>(),
Source => spec_fns::<source::SOURCE>(),
Color => spec_fns::<color::COLOR>(),
Image => spec_fns::<image::IMAGE>(),
Conference => spec_fns::<conference::CONFERENCE>(),
ParticipantType => spec_fns::<participant_type::PARTICIPANT_TYPE>(),
ResourceType => spec_fns::<resource_type::RESOURCE_TYPE>(),
CalendarAddress => spec_fns::<calendar_address::CALENDAR_ADDRESS>(),
LocationType => spec_fns::<location_type::LOCATION_TYPE>(),
StructuredData => spec_fns::<structured_data::STRUCTURED_DATA>(),
Link => spec_fns::<link::LINK>(),
Refid => spec_fns::<refid::REFID>(),
Concept => spec_fns::<concept::CONCEPT>(),
BusyType => spec_fns::<busytype::BUSYTYPE>(),
StyledDescription => spec_fns::<styled_description::STYLED_DESCRIPTION>(),
Acknowledged => spec_fns::<acknowledged::ACKNOWLEDGED>(),
Proximity => spec_fns::<proximity::PROXIMITY>(),
Tz => spec_fns::<tz::TZ>(),
AAlarm => spec_fns::<aalarm::AALARM>(),
DAlarm => spec_fns::<dalarm::DALARM>(),
MAlarm => spec_fns::<malarm::MALARM>(),
PAlarm => spec_fns::<palarm::PALARM>(),
RNum => spec_fns::<rnum::RNUM>(),
}
}
#[cfg(test)]
mod tests {
use crate::{
prop::{IcalPropKind, spec::prop_spec},
value::IcalValueKind,
version::IcalVersion,
};
#[test]
fn dispatches_every_property_onto_its_own_marker() {
for kind in IcalPropKind::ALL {
assert_eq!(prop_spec(kind).kind, kind, "{}", &*kind);
}
}
#[test]
fn every_property_states_a_value_kind_it_allows() {
for kind in IcalPropKind::ALL {
let spec = prop_spec(kind);
for version in (spec.allowed_versions)() {
let allowed = (spec.allowed_values)(*version);
let in_force = (spec.value)(*version, None);
assert!(!allowed.is_empty(), "{} allows no value kind", &*kind);
assert!(
allowed.contains(&in_force),
"{} decodes as {} which it does not allow",
&*kind,
&*in_force,
);
}
}
}
#[test]
fn a_declared_kind_wins_over_the_default() {
let spec = prop_spec(IcalPropKind::Attach);
assert_eq!(
(spec.value)(IcalVersion::V2_0, None),
IcalValueKind::Uri,
"the default is the first allowed kind"
);
assert_eq!(
(spec.value)(IcalVersion::V2_0, Some(IcalValueKind::Binary)),
IcalValueKind::Binary,
"a declared kind is honoured even outside the allowed set"
);
}
#[test]
fn a_list_property_stays_a_list_whatever_is_declared() {
let spec = prop_spec(IcalPropKind::RDate);
assert_eq!(
(spec.value)(IcalVersion::V2_0, Some(IcalValueKind::Period)),
IcalValueKind::DateTimeList
);
}
}