kbolt-types 0.1.3

Shared request and response types for kbolt
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AddScheduleRequest {
    pub trigger: ScheduleTrigger,
    pub scope: ScheduleScope,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleAddResponse {
    pub schedule: ScheduleDefinition,
    pub backend: ScheduleBackend,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleDefinition {
    pub id: String,
    pub trigger: ScheduleTrigger,
    pub scope: ScheduleScope,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ScheduleTrigger {
    Every {
        interval: ScheduleInterval,
    },
    Daily {
        time: String,
    },
    Weekly {
        weekdays: Vec<ScheduleWeekday>,
        time: String,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleInterval {
    pub value: u32,
    pub unit: ScheduleIntervalUnit,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScheduleIntervalUnit {
    Minutes,
    Hours,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum ScheduleWeekday {
    Mon,
    Tue,
    Wed,
    Thu,
    Fri,
    Sat,
    Sun,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ScheduleScope {
    All,
    Space {
        space: String,
    },
    Collections {
        space: String,
        collections: Vec<String>,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleStatusResponse {
    pub schedules: Vec<ScheduleStatusEntry>,
    pub orphans: Vec<ScheduleOrphan>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleStatusEntry {
    pub schedule: ScheduleDefinition,
    pub backend: ScheduleBackend,
    pub state: ScheduleState,
    pub run_state: ScheduleRunState,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScheduleBackend {
    Launchd,
    SystemdUser,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScheduleState {
    Installed,
    Drifted,
    TargetMissing,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ScheduleRunState {
    pub last_started: Option<String>,
    pub last_finished: Option<String>,
    pub last_result: Option<ScheduleRunResult>,
    pub last_error: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScheduleRunResult {
    Success,
    SkippedLock,
    Failed,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleOrphan {
    pub id: String,
    pub backend: ScheduleBackend,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum RemoveScheduleSelector {
    Id { id: String },
    All,
    Scope { scope: ScheduleScope },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RemoveScheduleRequest {
    pub selector: RemoveScheduleSelector,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScheduleRemoveResponse {
    pub removed_ids: Vec<String>,
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::{
        ScheduleBackend, ScheduleDefinition, ScheduleInterval, ScheduleIntervalUnit,
        ScheduleOrphan, ScheduleRunResult, ScheduleRunState, ScheduleScope, ScheduleState,
        ScheduleStatusEntry, ScheduleStatusResponse, ScheduleTrigger, ScheduleWeekday,
    };

    #[test]
    fn schedule_definition_serializes_weekly_collection_scope_as_snake_case() {
        let value = serde_json::to_value(ScheduleDefinition {
            id: "s2".to_string(),
            trigger: ScheduleTrigger::Weekly {
                weekdays: vec![ScheduleWeekday::Mon, ScheduleWeekday::Fri],
                time: "15:00".to_string(),
            },
            scope: ScheduleScope::Collections {
                space: "work".to_string(),
                collections: vec!["api".to_string(), "docs".to_string()],
            },
        })
        .expect("serialize schedule definition");

        assert_eq!(
            value,
            json!({
                "id": "s2",
                "trigger": {
                    "kind": "weekly",
                    "weekdays": ["mon", "fri"],
                    "time": "15:00"
                },
                "scope": {
                    "kind": "collections",
                    "space": "work",
                    "collections": ["api", "docs"]
                }
            })
        );
    }

    #[test]
    fn schedule_status_response_serializes_run_state_and_orphans() {
        let value = serde_json::to_value(ScheduleStatusResponse {
            schedules: vec![ScheduleStatusEntry {
                schedule: ScheduleDefinition {
                    id: "s1".to_string(),
                    trigger: ScheduleTrigger::Every {
                        interval: ScheduleInterval {
                            value: 30,
                            unit: ScheduleIntervalUnit::Minutes,
                        },
                    },
                    scope: ScheduleScope::All,
                },
                backend: ScheduleBackend::Launchd,
                state: ScheduleState::Installed,
                run_state: ScheduleRunState {
                    last_started: Some("2026-03-07T15:00:00Z".to_string()),
                    last_finished: Some("2026-03-07T15:00:09Z".to_string()),
                    last_result: Some(ScheduleRunResult::Success),
                    last_error: None,
                },
            }],
            orphans: vec![ScheduleOrphan {
                id: "s9".to_string(),
                backend: ScheduleBackend::SystemdUser,
            }],
        })
        .expect("serialize schedule status response");

        assert_eq!(
            value,
            json!({
                "schedules": [
                    {
                        "schedule": {
                            "id": "s1",
                            "trigger": {
                                "kind": "every",
                                "interval": {
                                    "value": 30,
                                    "unit": "minutes"
                                }
                            },
                            "scope": {
                                "kind": "all"
                            }
                        },
                        "backend": "launchd",
                        "state": "installed",
                        "run_state": {
                            "last_started": "2026-03-07T15:00:00Z",
                            "last_finished": "2026-03-07T15:00:09Z",
                            "last_result": "success",
                            "last_error": null
                        }
                    }
                ],
                "orphans": [
                    {
                        "id": "s9",
                        "backend": "systemd_user"
                    }
                ]
            })
        );
    }
}