use serde::{Deserialize, Serialize};
use crate::{CalendarId, EmailAddress, EventId, GrantId};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Event {
pub id: EventId,
pub grant_id: GrantId,
pub calendar_id: CalendarId,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub when: When,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(default = "default_busy")]
pub busy: bool,
#[serde(default)]
pub status: EventStatus,
#[serde(default)]
pub participants: Vec<Participant>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organizer: Option<EmailAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub creator: Option<EmailAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conferencing: Option<Conferencing>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recurrence: Option<Recurrence>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reminders: Option<Reminders>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capacity: Option<i32>,
#[serde(default)]
pub hide_participants: bool,
#[serde(default)]
pub read_only: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub html_link: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ical_uid: Option<String>,
#[serde(default)]
pub resources: Vec<Resource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility: Option<EventVisibility>,
#[serde(default = "default_event_object_type")]
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<i64>,
}
fn default_busy() -> bool {
true
}
fn default_event_object_type() -> String {
"event".to_string()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "object", rename_all = "lowercase")]
pub enum When {
Timespan {
start_time: i64,
end_time: i64,
#[serde(skip_serializing_if = "Option::is_none")]
start_timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
end_timezone: Option<String>,
},
Datespan {
start_date: String,
end_date: String,
},
Date {
date: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum EventStatus {
#[default]
Confirmed,
Tentative,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EventVisibility {
Default,
Public,
Private,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Participant {
pub email: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<ParticipantStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone_number: Option<String>,
}
impl Participant {
pub fn new(email: impl Into<String>) -> Self {
Self {
email: email.into(),
name: None,
status: None,
comment: None,
phone_number: None,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn status(mut self, status: ParticipantStatus) -> Self {
self.status = Some(status);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ParticipantStatus {
Yes,
No,
Maybe,
Noreply,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Conferencing {
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<ConferencingDetails>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConferencingDetails {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub meeting_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pin: Option<String>,
#[serde(default)]
pub phone: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Recurrence {
#[serde(rename = "rrule")]
pub rrule: Vec<String>,
#[serde(default)]
pub exdate: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Reminders {
#[serde(skip_serializing_if = "Option::is_none")]
pub use_default: Option<bool>,
#[serde(default)]
pub overrides: Vec<ReminderOverride>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReminderOverride {
pub reminder_minutes: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub reminder_method: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Resource {
pub email: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_creation() {
let event = Event {
id: EventId::new("event_123"),
grant_id: GrantId::new("grant_123"),
calendar_id: CalendarId::new("cal_123"),
title: Some("Test Event".to_string()),
description: None,
when: When::Timespan {
start_time: 1234567890,
end_time: 1234571490,
start_timezone: Some("UTC".to_string()),
end_timezone: Some("UTC".to_string()),
},
location: None,
busy: true,
status: EventStatus::Confirmed,
participants: vec![],
organizer: None,
creator: None,
conferencing: None,
recurrence: None,
reminders: None,
capacity: None,
hide_participants: false,
read_only: false,
html_link: None,
ical_uid: None,
resources: vec![],
visibility: None,
object: "event".to_string(),
metadata: None,
created_at: None,
updated_at: None,
};
assert_eq!(event.title, Some("Test Event".to_string()));
assert!(event.busy);
assert_eq!(event.status, EventStatus::Confirmed);
}
#[test]
fn test_when_timespan_serialization() {
let when = When::Timespan {
start_time: 1234567890,
end_time: 1234571490,
start_timezone: Some("America/New_York".to_string()),
end_timezone: Some("America/New_York".to_string()),
};
let json = serde_json::to_string(&when).unwrap();
assert!(json.contains("timespan"));
assert!(json.contains("1234567890"));
assert!(json.contains("America/New_York"));
let deserialized: When = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, when);
}
#[test]
fn test_when_datespan_serialization() {
let when = When::Datespan {
start_date: "2024-01-01".to_string(),
end_date: "2024-01-03".to_string(),
};
let json = serde_json::to_string(&when).unwrap();
assert!(json.contains("datespan"));
assert!(json.contains("2024-01-01"));
assert!(json.contains("2024-01-03"));
let deserialized: When = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, when);
}
#[test]
fn test_when_date_serialization() {
let when = When::Date {
date: "2024-01-15".to_string(),
};
let json = serde_json::to_string(&when).unwrap();
assert!(json.contains("date"));
assert!(json.contains("2024-01-15"));
let deserialized: When = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, when);
}
#[test]
fn test_participant_builder() {
let participant = Participant::new("user@example.com")
.name("John Doe")
.status(ParticipantStatus::Yes);
assert_eq!(participant.email, "user@example.com");
assert_eq!(participant.name, Some("John Doe".to_string()));
assert_eq!(participant.status, Some(ParticipantStatus::Yes));
}
#[test]
fn test_participant_status_serialization() {
let status = ParticipantStatus::Yes;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"yes\"");
let status = ParticipantStatus::Maybe;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"maybe\"");
}
#[test]
fn test_event_status_serialization() {
let status = EventStatus::Confirmed;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"confirmed\"");
let status = EventStatus::Cancelled;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"cancelled\"");
}
#[test]
fn test_conferencing_serialization() {
let conferencing = Conferencing {
provider: Some("Google Meet".to_string()),
details: Some(ConferencingDetails {
url: "https://meet.google.com/abc-def-ghi".to_string(),
meeting_code: Some("abc-def-ghi".to_string()),
password: None,
pin: None,
phone: vec![],
}),
};
let json = serde_json::to_string(&conferencing).unwrap();
assert!(json.contains("Google Meet"));
assert!(json.contains("meet.google.com"));
let deserialized: Conferencing = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, conferencing);
}
}