Skip to main content

ical/tree/component/
spec.rs

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