1use crate::{
7 param::IcalParamKind,
8 prop::IcalPropKind,
9 tree::{
10 param::COMMON_PARAMS,
11 prop::{
12 aalarm, acknowledged, action, attach, attendee, busytype, calendar_address, calscale,
13 cardinality::IcalPropCardinality, categories, class, color, comment, completed,
14 concept, conference, contact, created, dalarm, description, dtend, dtstamp, dtstart,
15 due, duration, exdate, exrule, freebusy, geo, image, last_modified, link, location,
16 location_type, malarm, method, name, organizer, palarm, participant_type,
17 percent_complete, priority, prodid, proximity, rdate, recurrence_id, refid,
18 refresh_interval, related_to, repeat, request_status, resource_type, resources, rnum,
19 rrule, sequence, source, status, structured_data, styled_description, summary, transp,
20 trigger, tz, tzid, tzname, tzoffsetfrom, tzoffsetto, tzurl, uid, url,
21 },
22 },
23 value::IcalValueKind,
24 version::IcalVersion,
25};
26
27pub trait IcalPropSpec {
36 const KIND: IcalPropKind;
38
39 fn allowed_versions() -> &'static [IcalVersion] {
41 &[IcalVersion::V1_0, IcalVersion::V2_0]
42 }
43
44 fn cardinality(_version: IcalVersion) -> IcalPropCardinality {
47 IcalPropCardinality::Any
48 }
49
50 fn allowed_values(_version: IcalVersion) -> &'static [IcalValueKind] {
53 &[IcalValueKind::Text]
54 }
55
56 fn allowed_params(_version: IcalVersion) -> &'static [IcalParamKind] {
58 COMMON_PARAMS
59 }
60
61 fn value(version: IcalVersion, declared: Option<IcalValueKind>) -> IcalValueKind {
65 declared
66 .or_else(|| Self::allowed_values(version).first().copied())
67 .unwrap_or(IcalValueKind::Text)
68 }
69}
70
71#[allow(dead_code)]
74pub(crate) struct IcalPropSpecFns {
75 pub kind: IcalPropKind,
78 pub allowed_versions: fn() -> &'static [IcalVersion],
80 pub cardinality: fn(IcalVersion) -> IcalPropCardinality,
82 pub allowed_values: fn(IcalVersion) -> &'static [IcalValueKind],
84 pub allowed_params: fn(IcalVersion) -> &'static [IcalParamKind],
86 pub value: fn(IcalVersion, Option<IcalValueKind>) -> IcalValueKind,
88}
89
90fn spec_fns<L: IcalPropSpec>() -> IcalPropSpecFns {
92 IcalPropSpecFns {
93 kind: L::KIND,
94 allowed_versions: L::allowed_versions,
95 cardinality: L::cardinality,
96 allowed_values: L::allowed_values,
97 allowed_params: L::allowed_params,
98 value: L::value,
99 }
100}
101
102pub(crate) fn prop_spec(prop: IcalPropKind) -> IcalPropSpecFns {
104 use IcalPropKind::*;
105
106 match prop {
107 CalScale => spec_fns::<calscale::CALSCALE>(),
108 Method => spec_fns::<method::METHOD>(),
109 ProdId => spec_fns::<prodid::PRODID>(),
110 Attach => spec_fns::<attach::ATTACH>(),
111 Categories => spec_fns::<categories::CATEGORIES>(),
112 Class => spec_fns::<class::CLASS>(),
113 Comment => spec_fns::<comment::COMMENT>(),
114 Description => spec_fns::<description::DESCRIPTION>(),
115 Geo => spec_fns::<geo::GEO>(),
116 Location => spec_fns::<location::LOCATION>(),
117 PercentComplete => spec_fns::<percent_complete::PERCENT_COMPLETE>(),
118 Priority => spec_fns::<priority::PRIORITY>(),
119 Resources => spec_fns::<resources::RESOURCES>(),
120 Status => spec_fns::<status::STATUS>(),
121 Summary => spec_fns::<summary::SUMMARY>(),
122 Completed => spec_fns::<completed::COMPLETED>(),
123 DtEnd => spec_fns::<dtend::DTEND>(),
124 Due => spec_fns::<due::DUE>(),
125 DtStart => spec_fns::<dtstart::DTSTART>(),
126 Duration => spec_fns::<duration::DURATION>(),
127 FreeBusy => spec_fns::<freebusy::FREEBUSY>(),
128 Transp => spec_fns::<transp::TRANSP>(),
129 TzId => spec_fns::<tzid::TZID>(),
130 TzName => spec_fns::<tzname::TZNAME>(),
131 TzOffsetFrom => spec_fns::<tzoffsetfrom::TZOFFSETFROM>(),
132 TzOffsetTo => spec_fns::<tzoffsetto::TZOFFSETTO>(),
133 TzUrl => spec_fns::<tzurl::TZURL>(),
134 Attendee => spec_fns::<attendee::ATTENDEE>(),
135 Contact => spec_fns::<contact::CONTACT>(),
136 Organizer => spec_fns::<organizer::ORGANIZER>(),
137 RecurrenceId => spec_fns::<recurrence_id::RECURRENCE_ID>(),
138 RelatedTo => spec_fns::<related_to::RELATED_TO>(),
139 Url => spec_fns::<url::URL>(),
140 Uid => spec_fns::<uid::UID>(),
141 ExDate => spec_fns::<exdate::EXDATE>(),
142 RDate => spec_fns::<rdate::RDATE>(),
143 RRule => spec_fns::<rrule::RRULE>(),
144 ExRule => spec_fns::<exrule::EXRULE>(),
145 Action => spec_fns::<action::ACTION>(),
146 Repeat => spec_fns::<repeat::REPEAT>(),
147 Trigger => spec_fns::<trigger::TRIGGER>(),
148 Created => spec_fns::<created::CREATED>(),
149 DtStamp => spec_fns::<dtstamp::DTSTAMP>(),
150 LastModified => spec_fns::<last_modified::LAST_MODIFIED>(),
151 Sequence => spec_fns::<sequence::SEQUENCE>(),
152 RequestStatus => spec_fns::<request_status::REQUEST_STATUS>(),
153 Name => spec_fns::<name::NAME>(),
154 RefreshInterval => spec_fns::<refresh_interval::REFRESH_INTERVAL>(),
155 Source => spec_fns::<source::SOURCE>(),
156 Color => spec_fns::<color::COLOR>(),
157 Image => spec_fns::<image::IMAGE>(),
158 Conference => spec_fns::<conference::CONFERENCE>(),
159 ParticipantType => spec_fns::<participant_type::PARTICIPANT_TYPE>(),
160 ResourceType => spec_fns::<resource_type::RESOURCE_TYPE>(),
161 CalendarAddress => spec_fns::<calendar_address::CALENDAR_ADDRESS>(),
162 LocationType => spec_fns::<location_type::LOCATION_TYPE>(),
163 StructuredData => spec_fns::<structured_data::STRUCTURED_DATA>(),
164 Link => spec_fns::<link::LINK>(),
165 Refid => spec_fns::<refid::REFID>(),
166 Concept => spec_fns::<concept::CONCEPT>(),
167 BusyType => spec_fns::<busytype::BUSYTYPE>(),
168 StyledDescription => spec_fns::<styled_description::STYLED_DESCRIPTION>(),
169 Acknowledged => spec_fns::<acknowledged::ACKNOWLEDGED>(),
170 Proximity => spec_fns::<proximity::PROXIMITY>(),
171 Tz => spec_fns::<tz::TZ>(),
172 AAlarm => spec_fns::<aalarm::AALARM>(),
173 DAlarm => spec_fns::<dalarm::DALARM>(),
174 MAlarm => spec_fns::<malarm::MALARM>(),
175 PAlarm => spec_fns::<palarm::PALARM>(),
176 RNum => spec_fns::<rnum::RNUM>(),
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::{
183 prop::IcalPropKind, tree::prop::spec::prop_spec, value::IcalValueKind, version::IcalVersion,
184 };
185
186 #[test]
187 fn dispatches_every_property_onto_its_own_marker() {
188 for kind in IcalPropKind::ALL {
192 assert_eq!(prop_spec(kind).kind, kind, "{}", &*kind);
193 }
194 }
195
196 #[test]
197 fn every_property_states_a_value_kind_it_allows() {
198 for kind in IcalPropKind::ALL {
199 let spec = prop_spec(kind);
200
201 for version in (spec.allowed_versions)() {
202 let allowed = (spec.allowed_values)(*version);
203 let in_force = (spec.value)(*version, None);
204
205 assert!(!allowed.is_empty(), "{} allows no value kind", &*kind);
209 assert!(
210 allowed.contains(&in_force),
211 "{} decodes as {} which it does not allow",
212 &*kind,
213 &*in_force,
214 );
215 }
216 }
217 }
218
219 #[test]
220 fn a_declared_kind_wins_over_the_default() {
221 let spec = prop_spec(IcalPropKind::Attach);
222
223 assert_eq!(
224 (spec.value)(IcalVersion::V2_0, None),
225 IcalValueKind::Uri,
226 "the default is the first allowed kind"
227 );
228 assert_eq!(
229 (spec.value)(IcalVersion::V2_0, Some(IcalValueKind::Binary)),
230 IcalValueKind::Binary,
231 "a declared kind is honoured even outside the allowed set"
232 );
233 }
234
235 #[test]
236 fn a_list_property_stays_a_list_whatever_is_declared() {
237 let spec = prop_spec(IcalPropKind::RDate);
238
239 assert_eq!(
242 (spec.value)(IcalVersion::V2_0, Some(IcalValueKind::Period)),
243 IcalValueKind::DateTimeList
244 );
245 }
246}