Skip to main content

ics_core/
event.rs

1use crate::profile::{google, icloud, microsoft};
2use crate::raw::{RawComponent, RawProperty};
3use chrono::{NaiveDate, NaiveDateTime};
4use serde::Serialize;
5
6#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
7#[serde(rename_all = "lowercase")]
8pub enum EventClass {
9    Public,
10    Private,
11    Confidential,
12}
13
14impl EventClass {
15    pub fn ics_value(self) -> &'static str {
16        match self {
17            EventClass::Public => "PUBLIC",
18            EventClass::Private => "PRIVATE",
19            EventClass::Confidential => "CONFIDENTIAL",
20        }
21    }
22
23    pub fn from_ics(s: &str) -> Option<Self> {
24        match s {
25            "PUBLIC" => Some(EventClass::Public),
26            "PRIVATE" => Some(EventClass::Private),
27            "CONFIDENTIAL" => Some(EventClass::Confidential),
28            _ => None,
29        }
30    }
31}
32
33/// RFC 5545 §3.8.2.7 `TRANSP` — time-transparency for free/busy lookups.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
35#[serde(rename_all = "lowercase")]
36pub enum Transp {
37    Transparent,
38    Opaque,
39}
40
41impl Transp {
42    pub fn ics_value(self) -> &'static str {
43        match self {
44            Transp::Transparent => "TRANSPARENT",
45            Transp::Opaque => "OPAQUE",
46        }
47    }
48
49    pub fn from_ics(s: &str) -> Option<Self> {
50        match s {
51            "TRANSPARENT" => Some(Transp::Transparent),
52            "OPAQUE" => Some(Transp::Opaque),
53            _ => None,
54        }
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize)]
59pub struct VEvent {
60    pub uid: String,
61    #[serde(serialize_with = "serialize_dtstamp")]
62    pub dtstamp: NaiveDateTime,
63    #[serde(serialize_with = "serialize_date")]
64    pub dtstart: NaiveDate,
65    #[serde(serialize_with = "serialize_date")]
66    pub dtend: NaiveDate,
67    pub summary: String,
68    /// RFC 5545 `TRANSP`.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub transp: Option<Transp>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub class: Option<EventClass>,
73    #[serde(skip_serializing_if = "Vec::is_empty")]
74    pub categories: Vec<String>,
75
76    /// Microsoft / Outlook event extension bundle. `X-MICROSOFT-CDO-BUSYSTATUS`
77    /// lives in `microsoft.busystatus` after ADR-001 Migration Step 4.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub microsoft: Option<microsoft::EventExtensions>,
80
81    /// Google calendar event extension bundle. Step 7 skeleton: no typed
82    /// fields yet; all matched properties land in `google.unrecognized`.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub google: Option<google::EventExtensions>,
85
86    /// Apple / iCloud event extension bundle. Step 7 skeleton: no typed
87    /// fields yet; all matched properties land in `icloud.unrecognized`.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub icloud: Option<icloud::EventExtensions>,
90
91    /// Properties matching no registered vendor prefix (ADR-001 rule 4).
92    #[serde(skip_serializing_if = "Vec::is_empty")]
93    pub unknown: Vec<RawProperty>,
94
95    /// Nested components the typed model does not understand (e.g.
96    /// `VALARM`). Preserved verbatim for ADR-001 / ADR-018 round-trip.
97    #[serde(skip_serializing_if = "Vec::is_empty")]
98    pub unrecognized_components: Vec<RawComponent>,
99}
100
101fn serialize_date<S: serde::Serializer>(date: &NaiveDate, s: S) -> Result<S::Ok, S::Error> {
102    s.serialize_str(&date.format("%Y-%m-%d").to_string())
103}
104
105fn serialize_dtstamp<S: serde::Serializer>(dt: &NaiveDateTime, s: S) -> Result<S::Ok, S::Error> {
106    s.serialize_str(&dt.format("%Y-%m-%dT%H:%M:%SZ").to_string())
107}