use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalendarEvent {
pub id: String,
pub title: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub location: Option<String>,
pub start: String,
pub end: String,
#[serde(default)]
pub all_day: bool,
#[serde(default)]
pub attendees: Vec<Attendee>,
#[serde(default)]
pub recurrence: Option<Recurrence>,
#[serde(default)]
pub reminders: Vec<u32>,
#[serde(default)]
pub calendar_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attendee {
pub email: String,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub status: AttendeeStatus,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum AttendeeStatus {
Accepted,
Declined,
Tentative,
#[default]
NeedsAction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Recurrence {
pub frequency: RecurrenceFreq,
#[serde(default = "default_interval")]
pub interval: u32,
#[serde(default)]
pub count: Option<u32>,
#[serde(default)]
pub until: Option<String>,
}
fn default_interval() -> u32 {
1
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum RecurrenceFreq {
Daily,
Weekly,
Monthly,
Yearly,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalendarInfo {
pub id: String,
pub name: String,
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub primary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FreeBusySlot {
pub start: String,
pub end: String,
pub status: BusyStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum BusyStatus {
Free,
Busy,
Tentative,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calendar_event_serde_roundtrip() {
let event = CalendarEvent {
id: "evt-1".to_string(),
title: "Team Meeting".to_string(),
description: Some("Weekly sync".to_string()),
location: Some("Room 101".to_string()),
start: "2025-06-01T10:00:00Z".to_string(),
end: "2025-06-01T11:00:00Z".to_string(),
all_day: false,
attendees: vec![Attendee {
email: "bob@example.com".to_string(),
name: Some("Bob".to_string()),
status: AttendeeStatus::Accepted,
}],
recurrence: Some(Recurrence {
frequency: RecurrenceFreq::Weekly,
interval: 1,
count: Some(10),
until: None,
}),
reminders: vec![15, 5],
calendar_id: Some("primary".to_string()),
};
let json = serde_json::to_string(&event).unwrap();
let deserialized: CalendarEvent = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.id, "evt-1");
assert_eq!(deserialized.title, "Team Meeting");
assert_eq!(deserialized.attendees.len(), 1);
assert_eq!(deserialized.attendees[0].status, AttendeeStatus::Accepted);
}
#[test]
fn test_attendee_status_default() {
let status = AttendeeStatus::default();
assert_eq!(status, AttendeeStatus::NeedsAction);
}
#[test]
fn test_recurrence_serde_roundtrip() {
let rec = Recurrence {
frequency: RecurrenceFreq::Monthly,
interval: 2,
count: None,
until: Some("2026-12-31T00:00:00Z".to_string()),
};
let json = serde_json::to_string(&rec).unwrap();
let deserialized: Recurrence = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.frequency, RecurrenceFreq::Monthly);
assert_eq!(deserialized.interval, 2);
}
#[test]
fn test_calendar_info_serde_roundtrip() {
let info = CalendarInfo {
id: "cal-1".to_string(),
name: "Work".to_string(),
color: Some("#4285f4".to_string()),
primary: true,
};
let json = serde_json::to_string(&info).unwrap();
let deserialized: CalendarInfo = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "Work");
assert!(deserialized.primary);
}
#[test]
fn test_free_busy_slot_serde_roundtrip() {
let slot = FreeBusySlot {
start: "2025-06-01T09:00:00Z".to_string(),
end: "2025-06-01T10:00:00Z".to_string(),
status: BusyStatus::Busy,
};
let json = serde_json::to_string(&slot).unwrap();
let deserialized: FreeBusySlot = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.status, BusyStatus::Busy);
}
}