ical/lib.rs
1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! # ical-rs
5//!
6//! One version-agnostic iCalendar library: a decoded model and a
7//! byte-faithful syntax tree that read and write vCalendar 1.0 (versit) and
8//! iCalendar 2.0 (RFC 5545, extended by 6638, 7529, 7953, 7986, 9073, 9074
9//! and 9253) alike.
10//!
11//! The version is a decoded indicator, never a type parameter: the tree
12//! ignores it, and only the codec and the per-property spec branch on it,
13//! where escaping or a value's shape genuinely differ.
14//!
15//! Unlike a flat address book, a calendar is a tree of components (events,
16//! to-dos, journals, free/busy, time zones, alarms), and the whole tree is
17//! parsed, walked and round-tripped.
18//!
19//! The crate is `no_std` (with `alloc`), its core is dependency-free, and
20//! every dependency sits behind a [cargo feature](#cargo-features).
21//!
22//! This header is the architecture; the behaviour behind it is specified
23//! capability by capability in the repository's cairn/spec folder.
24//!
25//! ## Postel's law
26//!
27//! Parsing is maximally liberal: any real calendar round-trips byte for byte,
28//! components, properties, parameters and value types no version defines
29//! included, and an `Unknown` arm on every open vocabulary carries that
30//! openness into the model.
31//!
32//! Strictness lives on the way out: the [`builder`] refuses to construct a
33//! property the spec forbids, and the [`validator`] checks a decoded calendar
34//! against its version's RFC contract. Neither needs the parser.
35//!
36//! ## The two layers
37//!
38//! The decoded model ([`ical`], [`component`], [`version`], [`prop`],
39//! [`param`], [`value`]) is pure data with no dependency on the syntax side,
40//! so it can be depended on alone, and so can everything reading it: the
41//! [`builder`], the [`validator`], [`recur`], [`tz`] and both JSON
42//! representations.
43//!
44//! Component, property and parameter names and value types are closed
45//! identity enums ([`IcalComponentKind`], [`IcalPropKind`],
46//! [`IcalParamKind`], [`IcalValueKind`]) whose wire spelling is reached
47//! through `FromStr` and `Deref`.
48//!
49//! A calendar is an [`Ical`]: a version, the calendar-level properties, and a
50//! list of nested [`IcalComponent`]s, themselves recursive.
51//!
52//! A property is an [`IcalProp`] of a name, parameters and one value, the
53//! last two open payload enums ([`IcalParam`], [`IcalValue`]) with an
54//! `Unknown` arm, so anything outside the model survives.
55//!
56//! The syntax tree ([`tree`], behind the default `parser` feature) is
57//! everything byte-faithful. Its hub is [`IcalCst`], a recursive tree of
58//! generic nodes reproducing the wire bytes exactly.
59//!
60//! Exactly means exactly: the tokeniser resolves a line's wire layout (its
61//! RFC 5545 3.1 folds, the blank lines before it, its `QUOTED-PRINTABLE` soft
62//! breaks) so every layer above sees one logical line, and records it on
63//! [`IcalWire`] so serialization lays it back out.
64//!
65//! Only an edit that changes a line's length drops that layout, since the
66//! recorded fold points no longer index the bytes they were taken against.
67//!
68//! [`parse`] reads one calendar and [`parse_many`] iterates a multi-calendar
69//! file, both strict, refusing a calendar they cannot structure.
70//!
71//! [`parse_recovering`] keeps what it cannot structure as opaque bytes,
72//! carries on, and reports what it worked around, for the calendars in the
73//! wild that a strict reading throws away whole.
74//!
75//! [`decode`] projects a CST onto the decoded [`Ical`], and `encode` (with
76//! `From<Ical>`) projects the model back to a canonical CST.
77//!
78//! A per-property lens ([`IcalPropLens`], implemented on the marker the
79//! property defines in [`prop`]) reads or edits one line through the
80//! byte-preserving [`cursor`]s, so editing one property leaves every other
81//! byte intact. A component marker keys the same access over a whole subtree.
82//!
83//! ## The spec layer
84//!
85//! Each property carries an [`IcalPropSpec`] on its marker (the versions it
86//! lives in, its cardinality, the value types and parameters it may take per
87//! version), and each component an [`IcalComponentSpec`] (the children it may
88//! nest and the properties it requires).
89//!
90//! A contract is what the RFC allows, so it is model rather than syntax: the
91//! markers live in [`prop`] and [`component`], and only their read-and-edit
92//! lens sits under [`tree`].
93//!
94//! One vtable dispatch bridges the open kinds back to those static specs, so
95//! the decoder, the [`validator`] and the [`builder`] all consult one source
96//! of truth.
97//!
98//! A calendar that passes earns an [`IcalValid`](validator::IcalValid) proof,
99//! and both `Ical` and `IcalValid<Ical>` convert back into an [`IcalCst`].
100//!
101//! ## Recurrence and time zones
102//!
103//! [`recur`] answers what a rule denotes, and what a whole component denotes:
104//! [`IcalRecurExpand`] walks one `RRULE`, and [`IcalRecurSet`] walks the set
105//! an event actually happens on, `RDATE`s, `EXDATE`s, `EXRULE`s and
106//! `RECURRENCE-ID` overrides included.
107//!
108//! Both are lazy, and both are civil: RFC 5545 expands on the local
109//! wall-clock time of `DTSTART`, so no offset is ever needed and none is ever
110//! resolved.
111//!
112//! [`tz`] is the step after, turning a civil occurrence into a UTC
113//! offset from the `VTIMEZONE` the calendar carries, and reporting the
114//! spring-forward gap and the fall-back fold rather than guessing.
115//!
116//! ## Reconciling two replicas
117//!
118//! [`merge`](tree::merge) is the syntax layer's answer to two divergent edits
119//! of one calendar: [`IcalMerge`] diffs each against their common base,
120//! reports what each did and where they collided, and builds the merged
121//! calendar out of the left side's own bytes.
122//!
123//! It lives under [`tree`] rather than over the model because keeping the
124//! bytes of every line neither side touched is the point.
125//!
126//! ## The JSON representations
127//!
128//! [`jcal`] is the RFC 7265 spelling of this model in JSON, member for
129//! member.
130//!
131//! [`jscalendar`] is the RFC 8984 data model, which is a different model: a
132//! `VCALENDAR` is a Group of Events and Tasks, a `DTEND` is a duration, an
133//! `ATTENDEE` line is a Participant object, and an overriding `VEVENT` is a
134//! patch inside the series it overrides.
135//!
136//! Both are lossless, each through an escape hatch of its own, and both take
137//! a raw [`serde_json::Value`] at the boundary rather than a serde
138//! implementation, since one model with two JSON spellings is exactly what
139//! serde cannot key.
140//!
141//! ## Cargo features
142//!
143//! `parser` (default) brings the byte-faithful [`tree`] and its codec, via
144//! the `memchr` crate. Everything under [`tree`] is gated on it; the decoded
145//! model, the builder, the validator, the recurrence layer, the time zones
146//! and both JSON representations are always available.
147//!
148//! Three content decoders are default too, one small crate each:
149//! `quoted-printable` decodes `QUOTED-PRINTABLE` value octets, `base64`
150//! decodes inline `BASE64` binary values, and `encoding` transcodes a foreign
151//! `CHARSET` to text through `encoding_rs` (the WHATWG Encoding Standard).
152//!
153//! `jcal` adds the RFC 7265 JSON representation, via the `serde_json` crate.
154//! `jscalendar` adds the RFC 8984 JSON data model, implies `jcal`, whose
155//! syntax carries the escape hatch, and pulls no crate of its own.
156//!
157//! [`IcalComponentKind`]: component::IcalComponentKind
158//! [`IcalPropKind`]: prop::IcalPropKind
159//! [`IcalParamKind`]: param::IcalParamKind
160//! [`IcalValueKind`]: value::IcalValueKind
161//! [`Ical`]: ical::Ical
162//! [`IcalComponent`]: component::IcalComponent
163//! [`IcalProp`]: prop::IcalProp
164//! [`IcalParam`]: param::IcalParam
165//! [`IcalValue`]: value::IcalValue
166//! [`IcalCst`]: tree::cst::IcalCst
167//! [`IcalWire`]: tree::wire::IcalWire
168//! [`parse`]: tree::cst::IcalCst::parse
169//! [`parse_many`]: tree::cst::IcalCst::parse_many
170//! [`parse_recovering`]: tree::cst::IcalCst::parse_recovering
171//! [`decode`]: tree::cst::IcalCst::decode
172//! [`IcalPropLens`]: tree::prop::lens::IcalPropLens
173//! [`cursor`]: tree::value::cursor::IcalValueCursor
174//! [`IcalPropSpec`]: prop::spec::IcalPropSpec
175//! [`IcalComponentSpec`]: component::spec::IcalComponentSpec
176//! [`IcalRecurExpand`]: recur::expand::IcalRecurExpand
177//! [`IcalRecurSet`]: recur::set::IcalRecurSet
178//! [`IcalMerge`]: tree::merge::IcalMerge
179
180extern crate alloc;
181
182pub mod builder;
183pub mod component;
184pub mod ical;
185pub mod param;
186pub mod prop;
187pub mod recur;
188pub mod tz;
189pub mod validator;
190pub mod value;
191pub mod version;
192
193#[cfg(feature = "jcal")]
194#[cfg_attr(docsrs, doc(cfg(feature = "jcal")))]
195pub mod jcal;
196
197#[cfg(feature = "jscalendar")]
198#[cfg_attr(docsrs, doc(cfg(feature = "jscalendar")))]
199pub mod jscalendar;
200
201#[cfg(feature = "parser")]
202pub mod tree;