Skip to main content

opendev_tools_impl/
update_todo.rs

1//! update_todo tool — update a single todo item's status or title.
2
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex};
5
6use opendev_runtime::{TodoManager, parse_status};
7use opendev_tools_core::{BaseTool, ToolContext, ToolResult};
8
9/// Tool that updates a single todo item by fuzzy ID.
10#[derive(Debug)]
11pub struct UpdateTodoTool {
12    manager: Arc<Mutex<TodoManager>>,
13}
14
15impl UpdateTodoTool {
16    pub fn new(manager: Arc<Mutex<TodoManager>>) -> Self {
17        Self { manager }
18    }
19}
20
21#[async_trait::async_trait]
22impl BaseTool for UpdateTodoTool {
23    fn name(&self) -> &str {
24        "update_todo"
25    }
26
27    fn description(&self) -> &str {
28        "Update a todo item's status or title. Supports fuzzy ID matching \
29         (e.g., '3', 'todo-3', 'todo_3', or partial title)."
30    }
31
32    fn parameter_schema(&self) -> serde_json::Value {
33        serde_json::json!({
34            "type": "object",
35            "properties": {
36                "id": {
37                    "type": "string",
38                    "description": "Todo item ID (e.g., '3', 'todo-3', 'todo_3', or partial title)"
39                },
40                "status": {
41                    "type": "string",
42                    "description": "New status: pending, in_progress, completed (or aliases: todo, doing, done)"
43                },
44                "title": {
45                    "type": "string",
46                    "description": "New title for the todo item"
47                }
48            },
49            "required": ["id"]
50        })
51    }
52
53    async fn execute(
54        &self,
55        args: HashMap<String, serde_json::Value>,
56        _ctx: &ToolContext,
57    ) -> ToolResult {
58        let id_str = match args.get("id").and_then(|v| v.as_str()) {
59            Some(s) => s,
60            None => {
61                // Also try numeric
62                match args.get("id").and_then(|v| v.as_u64()) {
63                    Some(n) => return self.do_update(&n.to_string(), &args),
64                    None => return ToolResult::fail("id is required"),
65                }
66            }
67        };
68
69        self.do_update(id_str, &args)
70    }
71}
72
73impl UpdateTodoTool {
74    fn do_update(&self, id_str: &str, args: &HashMap<String, serde_json::Value>) -> ToolResult {
75        let mut mgr = match self.manager.lock() {
76            Ok(m) => m,
77            Err(e) => return ToolResult::fail(format!("Lock error: {e}")),
78        };
79
80        let (id, _) = match mgr.find_todo(id_str) {
81            Some(found) => found,
82            None => return ToolResult::fail(format!("Todo not found: {id_str}")),
83        };
84
85        let mut changed = false;
86
87        // Update status if provided
88        if let Some(status_str) = args.get("status").and_then(|v| v.as_str()) {
89            if let Some(status) = parse_status(status_str) {
90                mgr.set_status(id, status);
91                changed = true;
92            } else {
93                return ToolResult::fail(format!(
94                    "Unknown status: {status_str}. Use: pending, in_progress, completed (or aliases: todo, doing, done)"
95                ));
96            }
97        }
98
99        // Update title if provided
100        if let Some(title) = args.get("title").and_then(|v| v.as_str())
101            && let Some(item) = mgr.todos_mut().get_mut(&id)
102        {
103            item.title = title.to_string();
104            changed = true;
105        }
106
107        if !changed {
108            return ToolResult::fail("No updates provided. Specify status and/or title.");
109        }
110
111        ToolResult::ok(mgr.format_status_sorted())
112    }
113}
114
115#[cfg(test)]
116#[path = "update_todo_tests.rs"]
117mod tests;