Skip to main content

ralph/
error_messages.rs

1//! Canonical error message constructors.
2//!
3//! Responsibilities:
4//! - Provide helper functions for all "task not found" error scenarios
5//! - Ensure consistent formatting and actionable hints
6//!
7//! Does not handle:
8//! - Error types (use anyhow/thiserror in consuming modules)
9//! - I18N (messages are English-only)
10//!
11//! Invariants/assumptions:
12//! - All task ID parameters are non-empty trimmed strings
13//! - Messages include actionable hints where appropriate
14
15fn queue_search_scope(include_done: bool) -> &'static str {
16    if include_done {
17        "queue or done archive"
18    } else {
19        "active queue"
20    }
21}
22
23fn task_not_found_in_scope(
24    subject: &str,
25    task_id: &str,
26    include_done: bool,
27    include_done_hint: bool,
28) -> String {
29    let mut message = format!(
30        "{subject} '{task_id}' not found in {}.",
31        queue_search_scope(include_done)
32    );
33    if !include_done && include_done_hint {
34        message.push_str(" Use --include-done to search the done archive.");
35    }
36    message
37}
38
39fn task_not_found_short(task_id: &str) -> String {
40    format!("Task not found: {task_id}")
41}
42
43/// Task not found in the active queue only.
44pub fn task_not_found_in_queue(task_id: &str) -> String {
45    task_not_found_in_scope("Task", task_id, false, false)
46}
47
48/// Task not found in either queue or done archive.
49pub fn task_not_found_in_queue_or_done(task_id: &str) -> String {
50    task_not_found_in_scope("Task", task_id, true, false)
51}
52
53/// Task not found with hint to use --include-done.
54pub fn task_not_found_with_include_done_hint(task_id: &str) -> String {
55    task_not_found_in_scope("Task", task_id, false, true)
56}
57
58/// Root task not found (for tree/graph commands).
59pub fn root_task_not_found(task_id: &str, include_done: bool) -> String {
60    task_not_found_in_scope("Root task", task_id, include_done, !include_done)
61}
62
63/// Source task not found (for clone/split operations).
64pub fn source_task_not_found(task_id: &str, search_done: bool) -> String {
65    task_not_found_in_scope("Source task", task_id, search_done, false)
66}
67
68/// Task not found for batch operations (recorded as failure).
69pub fn task_not_found_batch_failure(task_id: &str) -> String {
70    task_not_found_short(task_id)
71}
72
73/// Task not found with operation context (for QueueQueryError).
74pub fn task_not_found_with_operation(operation: &str, task_id: &str) -> String {
75    format!(
76        "Queue query failed (operation={operation}): \
77         target task not found: {task_id}. \
78         Ensure it exists in .ralph/queue.jsonc."
79    )
80}
81
82/// Task not found in done archive specifically.
83pub fn task_not_found_in_done_archive(task_id: &str, context: &str) -> String {
84    format!("Task '{task_id}' not found in done archive for {context}.")
85}
86
87/// Task not found for edit operations with file context.
88pub fn task_not_found_for_edit(operation: &str, task_id: &str) -> String {
89    format!(
90        "Queue {operation} failed (task_id={task_id}): \
91         task not found in .ralph/queue.jsonc."
92    )
93}
94
95/// Generic task not found (for simple cases).
96pub fn task_not_found(task_id: &str) -> String {
97    task_not_found_short(task_id)
98}
99
100/// Task no longer exists (for session recovery scenarios).
101/// Used when a task was deleted during execution.
102pub fn task_no_longer_exists(task_id: &str) -> String {
103    format!("Task '{task_id}' no longer exists in queue (may have been deleted or archived).")
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn task_not_found_in_active_queue_matches_contract() {
112        assert_eq!(
113            task_not_found_in_queue("RQ-100"),
114            "Task 'RQ-100' not found in active queue."
115        );
116    }
117
118    #[test]
119    fn task_not_found_in_queue_or_done_matches_contract() {
120        assert_eq!(
121            task_not_found_in_queue_or_done("RQ-100"),
122            "Task 'RQ-100' not found in queue or done archive."
123        );
124    }
125
126    #[test]
127    fn task_not_found_with_include_done_hint_matches_contract() {
128        assert_eq!(
129            task_not_found_with_include_done_hint("RQ-100"),
130            "Task 'RQ-100' not found in active queue. Use --include-done to search the done archive."
131        );
132    }
133
134    #[test]
135    fn root_task_not_found_without_done_search_includes_hint() {
136        assert_eq!(
137            root_task_not_found("RQ-100", false),
138            "Root task 'RQ-100' not found in active queue. Use --include-done to search the done archive."
139        );
140    }
141
142    #[test]
143    fn root_task_not_found_with_done_search_matches_contract() {
144        assert_eq!(
145            root_task_not_found("RQ-100", true),
146            "Root task 'RQ-100' not found in queue or done archive."
147        );
148    }
149
150    #[test]
151    fn source_task_not_found_without_done_search_matches_contract() {
152        assert_eq!(
153            source_task_not_found("RQ-100", false),
154            "Source task 'RQ-100' not found in active queue."
155        );
156    }
157
158    #[test]
159    fn source_task_not_found_with_done_search_matches_contract() {
160        assert_eq!(
161            source_task_not_found("RQ-100", true),
162            "Source task 'RQ-100' not found in queue or done archive."
163        );
164    }
165
166    #[test]
167    fn generic_task_not_found_matches_contract() {
168        assert_eq!(task_not_found("RQ-100"), "Task not found: RQ-100");
169    }
170
171    #[test]
172    fn batch_task_not_found_matches_contract() {
173        assert_eq!(
174            task_not_found_batch_failure("RQ-100"),
175            "Task not found: RQ-100"
176        );
177    }
178
179    #[test]
180    fn task_not_found_for_edit_matches_contract() {
181        assert_eq!(
182            task_not_found_for_edit("status", "RQ-100"),
183            "Queue status failed (task_id=RQ-100): task not found in .ralph/queue.jsonc."
184        );
185    }
186
187    #[test]
188    fn task_not_found_with_operation_matches_contract() {
189        assert_eq!(
190            task_not_found_with_operation("edit", "RQ-100"),
191            "Queue query failed (operation=edit): target task not found: RQ-100. Ensure it exists in .ralph/queue.jsonc."
192        );
193    }
194
195    #[test]
196    fn task_not_found_in_done_archive_matches_contract() {
197        assert_eq!(
198            task_not_found_in_done_archive("RQ-100", "restore"),
199            "Task 'RQ-100' not found in done archive for restore."
200        );
201    }
202
203    #[test]
204    fn task_no_longer_exists_matches_contract() {
205        assert_eq!(
206            task_no_longer_exists("RQ-100"),
207            "Task 'RQ-100' no longer exists in queue (may have been deleted or archived)."
208        );
209    }
210}