nylas-types 0.1.1

Type definitions for Nylas API v3
Documentation
//! Availability and free-busy types for the Nylas API v3.

use serde::{Deserialize, Serialize};

/// A time slot representing available or free-busy time for participants.
///
/// # Example
///
/// ```
/// # use nylas_types::TimeSlot;
/// let slot = TimeSlot {
///     emails: vec!["user@example.com".to_string()],
///     start_time: 1234567890,
///     end_time: 1234571490,
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeSlot {
    /// List of email addresses for which this time slot is available.
    pub emails: Vec<String>,

    /// Unix timestamp indicating the start of the time slot.
    pub start_time: i64,

    /// Unix timestamp indicating the end of the time slot.
    pub end_time: i64,
}

impl TimeSlot {
    /// Create a new time slot.
    pub fn new(emails: Vec<String>, start_time: i64, end_time: i64) -> Self {
        Self {
            emails,
            start_time,
            end_time,
        }
    }
}

/// Response from the availability endpoint containing available time slots.
///
/// # Example
///
/// ```
/// # use nylas_types::{AvailabilityResponse, TimeSlot};
/// let response = AvailabilityResponse {
///     time_slots: vec![
///         TimeSlot::new(
///             vec!["user@example.com".to_string()],
///             1234567890,
///             1234571490,
///         ),
///     ],
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AvailabilityResponse {
    /// List of available time slots for the queried participants.
    pub time_slots: Vec<TimeSlot>,
}

impl AvailabilityResponse {
    /// Create a new availability response.
    pub fn new(time_slots: Vec<TimeSlot>) -> Self {
        Self { time_slots }
    }
}

/// Request for querying availability/free-busy information.
///
/// # Example
///
/// ```
/// # use nylas_types::AvailabilityRequest;
/// let request = AvailabilityRequest::builder()
///     .email("user@example.com")
///     .duration_minutes(30)
///     .start_time(1234567890)
///     .end_time(1234654290)
///     .build();
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AvailabilityRequest {
    /// List of email addresses to check availability for.
    pub emails: Vec<String>,

    /// Duration of requested availability slots in minutes.
    pub duration_minutes: u32,

    /// Optional interval between availability slots in minutes.
    /// If not specified, defaults to the duration.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub interval_minutes: Option<u32>,

    /// Unix timestamp indicating the start of the time range to check.
    pub start_time: i64,

    /// Unix timestamp indicating the end of the time range to check.
    pub end_time: i64,
}

impl AvailabilityRequest {
    /// Create a builder for constructing an AvailabilityRequest.
    pub fn builder() -> AvailabilityRequestBuilder {
        AvailabilityRequestBuilder {
            emails: Vec::new(),
            duration_minutes: None,
            interval_minutes: None,
            start_time: None,
            end_time: None,
        }
    }

    /// Create a new availability request with required fields.
    pub fn new(emails: Vec<String>, duration_minutes: u32, start_time: i64, end_time: i64) -> Self {
        Self {
            emails,
            duration_minutes,
            interval_minutes: None,
            start_time,
            end_time,
        }
    }
}

/// Builder for constructing AvailabilityRequest.
#[derive(Debug, Clone)]
pub struct AvailabilityRequestBuilder {
    emails: Vec<String>,
    duration_minutes: Option<u32>,
    interval_minutes: Option<u32>,
    start_time: Option<i64>,
    end_time: Option<i64>,
}

impl AvailabilityRequestBuilder {
    /// Add a single email address to the request.
    pub fn email(mut self, email: impl Into<String>) -> Self {
        self.emails.push(email.into());
        self
    }

    /// Add multiple email addresses to the request.
    pub fn emails(mut self, emails: Vec<String>) -> Self {
        self.emails.extend(emails);
        self
    }

    /// Set the duration of requested availability slots in minutes.
    pub fn duration_minutes(mut self, duration: u32) -> Self {
        self.duration_minutes = Some(duration);
        self
    }

    /// Set the interval between availability slots in minutes.
    pub fn interval_minutes(mut self, interval: u32) -> Self {
        self.interval_minutes = Some(interval);
        self
    }

    /// Set the start time for the availability query.
    pub fn start_time(mut self, start_time: i64) -> Self {
        self.start_time = Some(start_time);
        self
    }

    /// Set the end time for the availability query.
    pub fn end_time(mut self, end_time: i64) -> Self {
        self.end_time = Some(end_time);
        self
    }

    /// Build the AvailabilityRequest.
    ///
    /// # Panics
    ///
    /// Panics if required fields (emails, duration_minutes, start_time, end_time) are not set.
    pub fn build(self) -> AvailabilityRequest {
        AvailabilityRequest {
            emails: if self.emails.is_empty() {
                panic!("At least one email address is required")
            } else {
                self.emails
            },
            duration_minutes: self.duration_minutes.expect("duration_minutes is required"),
            interval_minutes: self.interval_minutes,
            start_time: self.start_time.expect("start_time is required"),
            end_time: self.end_time.expect("end_time is required"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_time_slot_creation() {
        let slot = TimeSlot::new(vec!["user@example.com".to_string()], 1234567890, 1234571490);

        assert_eq!(slot.emails.len(), 1);
        assert_eq!(slot.emails[0], "user@example.com");
        assert_eq!(slot.start_time, 1234567890);
        assert_eq!(slot.end_time, 1234571490);
    }

    #[test]
    fn test_time_slot_serialization() {
        let slot = TimeSlot::new(vec!["user@example.com".to_string()], 1234567890, 1234571490);

        let json = serde_json::to_string(&slot).unwrap();
        assert!(json.contains("user@example.com"));
        assert!(json.contains("1234567890"));
        assert!(json.contains("1234571490"));

        let deserialized: TimeSlot = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, slot);
    }

    #[test]
    fn test_availability_response_creation() {
        let slots = vec![TimeSlot::new(
            vec!["user@example.com".to_string()],
            1234567890,
            1234571490,
        )];

        let response = AvailabilityResponse::new(slots.clone());
        assert_eq!(response.time_slots.len(), 1);
        assert_eq!(response.time_slots[0], slots[0]);
    }

    #[test]
    fn test_availability_response_serialization() {
        let response = AvailabilityResponse::new(vec![TimeSlot::new(
            vec!["user@example.com".to_string()],
            1234567890,
            1234571490,
        )]);

        let json = serde_json::to_string(&response).unwrap();
        assert!(json.contains("user@example.com"));

        let deserialized: AvailabilityResponse = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, response);
    }

    #[test]
    fn test_availability_request_new() {
        let request = AvailabilityRequest::new(
            vec!["user@example.com".to_string()],
            30,
            1234567890,
            1234654290,
        );

        assert_eq!(request.emails.len(), 1);
        assert_eq!(request.emails[0], "user@example.com");
        assert_eq!(request.duration_minutes, 30);
        assert_eq!(request.start_time, 1234567890);
        assert_eq!(request.end_time, 1234654290);
        assert_eq!(request.interval_minutes, None);
    }

    #[test]
    fn test_availability_request_builder_single_email() {
        let request = AvailabilityRequest::builder()
            .email("user@example.com")
            .duration_minutes(30)
            .start_time(1234567890)
            .end_time(1234654290)
            .build();

        assert_eq!(request.emails.len(), 1);
        assert_eq!(request.emails[0], "user@example.com");
        assert_eq!(request.duration_minutes, 30);
    }

    #[test]
    fn test_availability_request_builder_multiple_emails() {
        let request = AvailabilityRequest::builder()
            .email("user1@example.com")
            .email("user2@example.com")
            .duration_minutes(45)
            .interval_minutes(15)
            .start_time(1234567890)
            .end_time(1234654290)
            .build();

        assert_eq!(request.emails.len(), 2);
        assert_eq!(request.duration_minutes, 45);
        assert_eq!(request.interval_minutes, Some(15));
    }

    #[test]
    fn test_availability_request_builder_with_emails_vec() {
        let emails = vec![
            "user1@example.com".to_string(),
            "user2@example.com".to_string(),
        ];

        let request = AvailabilityRequest::builder()
            .emails(emails)
            .duration_minutes(60)
            .start_time(1234567890)
            .end_time(1234654290)
            .build();

        assert_eq!(request.emails.len(), 2);
        assert_eq!(request.duration_minutes, 60);
    }

    #[test]
    #[should_panic(expected = "At least one email address is required")]
    fn test_availability_request_builder_no_emails() {
        AvailabilityRequest::builder()
            .duration_minutes(30)
            .start_time(1234567890)
            .end_time(1234654290)
            .build();
    }

    #[test]
    #[should_panic(expected = "duration_minutes is required")]
    fn test_availability_request_builder_no_duration() {
        AvailabilityRequest::builder()
            .email("user@example.com")
            .start_time(1234567890)
            .end_time(1234654290)
            .build();
    }

    #[test]
    #[should_panic(expected = "start_time is required")]
    fn test_availability_request_builder_no_start_time() {
        AvailabilityRequest::builder()
            .email("user@example.com")
            .duration_minutes(30)
            .end_time(1234654290)
            .build();
    }

    #[test]
    fn test_availability_request_serialization() {
        let request = AvailabilityRequest::builder()
            .email("user@example.com")
            .duration_minutes(30)
            .interval_minutes(15)
            .start_time(1234567890)
            .end_time(1234654290)
            .build();

        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("user@example.com"));
        assert!(json.contains("30"));
        assert!(json.contains("15"));

        let deserialized: AvailabilityRequest = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, request);
    }

    #[test]
    fn test_availability_request_serialization_no_interval() {
        let request = AvailabilityRequest::new(
            vec!["user@example.com".to_string()],
            30,
            1234567890,
            1234654290,
        );

        let json = serde_json::to_string(&request).unwrap();
        // interval_minutes should not be serialized when None
        assert!(!json.contains("interval_minutes"));

        let deserialized: AvailabilityRequest = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, request);
    }
}