Skip to main content

rover/tasks/
types.rs

1//! Public types shared between the scheduler, workers, and the CLI.
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6pub use crate::storage::tasks::{TaskKind, TaskStatus};
7
8/// Bare UUIDv7 task identifier.
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct TaskId(pub String);
12
13impl TaskId {
14    pub fn new() -> Self {
15        Self(Uuid::now_v7().to_string())
16    }
17
18    pub fn parse(s: &str) -> Result<Self, uuid::Error> {
19        Uuid::parse_str(s)?;
20        Ok(Self(s.to_string()))
21    }
22
23    pub fn as_str(&self) -> &str {
24        &self.0
25    }
26}
27
28impl std::fmt::Display for TaskId {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.write_str(&self.0)
31    }
32}
33
34impl Default for TaskId {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40/// Core event kinds shared by every worker (per design spec ยง5.1).
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum CoreEvent {
44    TaskStarted,
45    TaskCompleted,
46    TaskFailed,
47    TaskCancelled,
48}
49
50impl CoreEvent {
51    pub fn as_str(self) -> &'static str {
52        match self {
53            Self::TaskStarted => "task_started",
54            Self::TaskCompleted => "task_completed",
55            Self::TaskFailed => "task_failed",
56            Self::TaskCancelled => "task_cancelled",
57        }
58    }
59}
60
61/// `batch_fetch` params stored in `tasks.params_json`.
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63pub struct BatchFetchParams {
64    pub urls: Vec<String>,
65    #[serde(default = "default_concurrency")]
66    pub concurrency: u32,
67    #[serde(default = "default_per_domain")]
68    pub per_domain_concurrency: u32,
69    #[serde(default)]
70    pub force_refresh: bool,
71}
72
73fn default_concurrency() -> u32 {
74    8
75}
76fn default_per_domain() -> u32 {
77    2
78}
79
80/// `retry` params.
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82pub struct RetryParams {
83    pub url: String,
84    pub attempt: u8,
85    pub wait_ms_initial: u64,
86    pub max_attempts: u8,
87    #[serde(default)]
88    pub parent_task_id: Option<String>,
89}
90
91/// `revalidate` params.
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
93pub struct RevalidateParams {
94    pub url: String,
95    #[serde(default)]
96    pub etag_at_serve: Option<String>,
97    #[serde(default)]
98    pub last_modified_at_serve: Option<String>,
99}
100
101/// Rollup written to `tasks.result_json` when a `batch_fetch` completes.
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
103pub struct BatchFetchResult {
104    pub total: u32,
105    pub succeeded: u32,
106    pub failed: u32,
107    pub duration_ms: i64,
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn task_id_is_uuid_v7_string() {
116        let id = TaskId::new();
117        let parsed = Uuid::parse_str(id.as_str()).unwrap();
118        assert_eq!(parsed.get_version_num(), 7);
119    }
120
121    #[test]
122    fn task_id_parse_roundtrip() {
123        let id = TaskId::new();
124        let again = TaskId::parse(id.as_str()).unwrap();
125        assert_eq!(id, again);
126    }
127
128    #[test]
129    fn task_id_parse_rejects_garbage() {
130        assert!(TaskId::parse("not-a-uuid").is_err());
131    }
132
133    #[test]
134    fn batch_fetch_params_defaults() {
135        let v: BatchFetchParams = serde_json::from_str(r#"{"urls":["a"]}"#).unwrap();
136        assert_eq!(v.concurrency, 8);
137        assert_eq!(v.per_domain_concurrency, 2);
138        assert!(!v.force_refresh);
139    }
140
141    #[test]
142    fn core_event_as_str_table() {
143        assert_eq!(CoreEvent::TaskStarted.as_str(), "task_started");
144        assert_eq!(CoreEvent::TaskFailed.as_str(), "task_failed");
145    }
146}