Skip to main content

ralph/queue/operations/
fields.rs

1//! Custom field mutation helpers for queue tasks.
2//!
3//! Responsibilities:
4//! - Set or update a single custom field on a task.
5//! - Validate task IDs, custom field keys, and timestamps.
6//! - Preview custom field changes without applying them.
7//!
8//! Does not handle:
9//! - Persisting queue files to disk or resolving task selection rules.
10//! - Validating non-custom task fields.
11//!
12//! Assumptions/invariants:
13//! - Callers supply a loaded `QueueFile` and RFC3339 `now` timestamp.
14//! - Custom field keys are non-empty and contain no whitespace.
15
16use super::validate::{ensure_task_id, parse_rfc3339_utc, validate_custom_field_key};
17use crate::contracts::QueueFile;
18use anyhow::{Result, anyhow};
19
20/// Preview of what would change in a custom field set operation.
21#[derive(Debug, Clone)]
22pub struct TaskFieldPreview {
23    pub task_id: String,
24    pub key: String,
25    pub old_value: Option<String>,
26    pub new_value: String,
27}
28
29/// Preview custom field changes without applying them.
30pub fn preview_set_field(
31    queue: &QueueFile,
32    task_id: &str,
33    key: &str,
34    value: &str,
35) -> Result<TaskFieldPreview> {
36    let operation = "preview field set";
37    let key_trimmed = key.trim();
38    let needle = ensure_task_id(task_id, operation)?;
39    validate_custom_field_key(key_trimmed, needle, operation)?;
40
41    let task = queue
42        .tasks
43        .iter()
44        .find(|t| t.id.trim() == needle)
45        .ok_or_else(|| {
46            anyhow!(
47                "{}",
48                crate::error_messages::task_not_found_for_edit(operation, needle)
49            )
50        })?;
51
52    let old_value = task.custom_fields.get(key_trimmed).cloned();
53    let new_value = value.trim().to_string();
54
55    Ok(TaskFieldPreview {
56        task_id: needle.to_string(),
57        key: key_trimmed.to_string(),
58        old_value,
59        new_value,
60    })
61}
62
63pub fn set_field(
64    queue: &mut QueueFile,
65    task_id: &str,
66    key: &str,
67    value: &str,
68    now_rfc3339: &str,
69) -> Result<()> {
70    let operation = "custom field set";
71    let key_trimmed = key.trim();
72    let needle = ensure_task_id(task_id, operation)?;
73    validate_custom_field_key(key_trimmed, needle, operation)?;
74
75    let now = parse_rfc3339_utc(now_rfc3339)?;
76
77    let task = queue
78        .tasks
79        .iter_mut()
80        .find(|t| t.id.trim() == needle)
81        .ok_or_else(|| {
82            anyhow!(
83                "{}",
84                crate::error_messages::task_not_found_for_edit(operation, needle)
85            )
86        })?;
87
88    task.custom_fields
89        .insert(key_trimmed.to_string(), value.trim().to_string());
90    task.updated_at = Some(now);
91
92    Ok(())
93}