Skip to main content

ical/component/
spec.rs

1//! # Component spec
2//!
3//! The per-component contract on the component markers (the child components it
4//! may nest and the properties it requires), and the runtime vtable that
5//! bridges the open [`IcalComponentKind`] back to those static impls.
6
7use crate::{
8    component::{
9        IcalComponentKind, available, daylight, participant, standard, valarm, vavailability,
10        vcalendar, vevent, vfreebusy, vjournal, vlocation, vresource, vtimezone, vtodo,
11    },
12    prop::IcalPropKind,
13};
14
15/// The per-component contract: the child components it may nest and the
16/// properties it requires. Implemented on the zero-sized component markers; the
17/// defaults are the permissive empty sets, so a component overrides only where
18/// it constrains.
19pub trait IcalComponentSpec {
20    /// The component this spec describes.
21    const KIND: IcalComponentKind;
22
23    /// The child components this one may directly nest.
24    fn allowed_children() -> &'static [IcalComponentKind] {
25        &[]
26    }
27
28    /// The properties this component requires.
29    fn required_props() -> &'static [IcalPropKind] {
30        &[]
31    }
32}
33
34/// The spec of a component as function pointers, the runtime bridge from the
35/// open [`IcalComponentKind`] back to the static per-marker impls.
36#[allow(dead_code)]
37pub(crate) struct IcalComponentSpecFns {
38    /// The component this spec describes, so the dispatch can be checked
39    /// against itself.
40    pub kind: IcalComponentKind,
41    /// See [`IcalComponentSpec::allowed_children`].
42    pub allowed_children: fn() -> &'static [IcalComponentKind],
43    /// See [`IcalComponentSpec::required_props`].
44    pub required_props: fn() -> &'static [IcalPropKind],
45}
46
47/// Collect the spec function pointers of a marker type.
48fn spec_fns<C: IcalComponentSpec>() -> IcalComponentSpecFns {
49    IcalComponentSpecFns {
50        kind: C::KIND,
51        allowed_children: C::allowed_children,
52        required_props: C::required_props,
53    }
54}
55
56/// Dispatch a component kind onto its marker spec.
57pub(crate) fn component_spec(component: IcalComponentKind) -> IcalComponentSpecFns {
58    use IcalComponentKind::*;
59
60    match component {
61        VCalendar => spec_fns::<vcalendar::VCALENDAR>(),
62        VEvent => spec_fns::<vevent::VEVENT>(),
63        VTodo => spec_fns::<vtodo::VTODO>(),
64        VJournal => spec_fns::<vjournal::VJOURNAL>(),
65        VFreeBusy => spec_fns::<vfreebusy::VFREEBUSY>(),
66        VTimezone => spec_fns::<vtimezone::VTIMEZONE>(),
67        Standard => spec_fns::<standard::STANDARD>(),
68        Daylight => spec_fns::<daylight::DAYLIGHT>(),
69        VAlarm => spec_fns::<valarm::VALARM>(),
70        Participant => spec_fns::<participant::PARTICIPANT>(),
71        VLocation => spec_fns::<vlocation::VLOCATION>(),
72        VResource => spec_fns::<vresource::VRESOURCE>(),
73        VAvailability => spec_fns::<vavailability::VAVAILABILITY>(),
74        Available => spec_fns::<available::AVAILABLE>(),
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use alloc::vec::Vec;
81
82    use crate::component::{IcalComponentKind, spec::component_spec};
83
84    #[test]
85    fn dispatches_every_component_onto_its_own_marker() {
86        for kind in IcalComponentKind::ALL {
87            assert_eq!(component_spec(kind).kind, kind, "{}", &*kind);
88        }
89    }
90
91    #[test]
92    fn no_component_requires_one_property_twice() {
93        for kind in IcalComponentKind::ALL {
94            let spec = component_spec(kind);
95
96            let mut required: Vec<&str> = (spec.required_props)().iter().map(|p| &**p).collect();
97            let count = required.len();
98            required.sort_unstable();
99            required.dedup();
100
101            assert_eq!(
102                required.len(),
103                count,
104                "{} requires a property twice",
105                &*kind
106            );
107        }
108    }
109}