ralph/queue/operations/batch/
validation.rs1use crate::contracts::{QueueFile, Task};
18
19use super::{BatchOperationResult, BatchTaskResult};
20
21pub fn collect_task_ids(tasks: &[&Task]) -> Vec<String> {
23 tasks.iter().map(|task| task.id.clone()).collect()
24}
25
26pub(crate) fn deduplicate_task_ids(task_ids: &[String]) -> Vec<String> {
28 let mut seen = std::collections::HashSet::new();
29 let mut result = Vec::new();
30 for id in task_ids {
31 let trimmed = id.trim().to_string();
32 if !trimmed.is_empty() && seen.insert(trimmed.clone()) {
33 result.push(trimmed);
34 }
35 }
36 result
37}
38
39pub(crate) fn validate_task_ids_exist(
43 queue: &QueueFile,
44 task_ids: &[String],
45) -> anyhow::Result<()> {
46 use anyhow::bail;
47
48 for task_id in task_ids {
49 let needle = task_id.trim();
50 if needle.is_empty() {
51 bail!("Empty task ID provided");
52 }
53 if !queue.tasks.iter().any(|task| task.id.trim() == needle) {
54 bail!(
55 "{}",
56 crate::error_messages::task_not_found_batch_failure(needle)
57 );
58 }
59 }
60 Ok(())
61}
62
63pub(crate) struct BatchResultCollector {
74 total: usize,
75 results: Vec<BatchTaskResult>,
76 succeeded: usize,
77 failed: usize,
78 continue_on_error: bool,
79 op_name: &'static str,
80}
81
82impl BatchResultCollector {
83 pub fn new(total: usize, continue_on_error: bool, op_name: &'static str) -> Self {
85 Self {
86 total,
87 results: Vec::with_capacity(total),
88 succeeded: 0,
89 failed: 0,
90 continue_on_error,
91 op_name,
92 }
93 }
94
95 pub fn record_success(&mut self, task_id: String, created_task_ids: Vec<String>) {
97 self.results.push(BatchTaskResult {
98 task_id,
99 success: true,
100 error: None,
101 created_task_ids,
102 });
103 self.succeeded += 1;
104 }
105
106 pub fn record_failure(&mut self, task_id: String, error: String) -> anyhow::Result<()> {
110 self.results.push(BatchTaskResult {
111 task_id: task_id.clone(),
112 success: false,
113 error: Some(error.clone()),
114 created_task_ids: Vec::new(),
115 });
116 self.failed += 1;
117
118 if !self.continue_on_error {
119 anyhow::bail!(
120 "Batch {} failed at task {}: {}. Use --continue-on-error to process remaining tasks.",
121 self.op_name,
122 task_id,
123 error
124 );
125 }
126 Ok(())
127 }
128
129 pub fn finish(self) -> BatchOperationResult {
131 BatchOperationResult {
132 total: self.total,
133 succeeded: self.succeeded,
134 failed: self.failed,
135 results: self.results,
136 }
137 }
138}
139
140pub(crate) fn preprocess_batch_ids(
144 task_ids: &[String],
145 op_name: &str,
146) -> anyhow::Result<Vec<String>> {
147 let unique_ids = deduplicate_task_ids(task_ids);
148 if unique_ids.is_empty() {
149 anyhow::bail!("No task IDs provided for batch {}", op_name);
150 }
151 Ok(unique_ids)
152}