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