1use crate::{ActionError, ActionResult, ErrorCode, Value};
4use alloc::{string::String, vec::Vec};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum Status {
10 Pending,
12 Running,
14 Cancelling,
16 Complete,
18 Failed,
20 Cancelled,
22}
23
24impl Status {
25 pub fn as_str(self) -> &'static str {
27 match self {
28 Self::Pending => "pending",
29 Self::Running => "running",
30 Self::Cancelling => "cancelling",
31 Self::Complete => "complete",
32 Self::Failed => "failed",
33 Self::Cancelled => "cancelled",
34 }
35 }
36 pub fn terminal(self) -> bool {
38 matches!(self, Self::Complete | Self::Failed | Self::Cancelled)
39 }
40}
41
42#[derive(Clone, Debug)]
44pub struct Task {
45 pub id: u64,
47 pub action: String,
49 pub status: Status,
51 pub progress: Option<u16>,
53 pub message: String,
55 pub cancellable: bool,
57 pub result: Option<ActionResult>,
59 pub error: Option<ActionError>,
61}
62
63impl Task {
64 pub fn to_json(&self) -> Value {
66 Value::object([
67 ("id", Value::u64(self.id)),
68 ("action", Value::string(&self.action)),
69 ("status", Value::string(self.status.as_str())),
70 (
71 "progress",
72 self.progress
73 .map(|p| Value::u64(p as u64))
74 .unwrap_or(Value::Null),
75 ),
76 ("message", Value::string(&self.message)),
77 ("cancellable", Value::Bool(self.cancellable)),
78 (
79 "result",
80 self.result
81 .as_ref()
82 .map(ActionResult::to_json)
83 .unwrap_or(Value::Null),
84 ),
85 (
86 "error",
87 self.error
88 .as_ref()
89 .map(ActionError::to_json)
90 .unwrap_or(Value::Null),
91 ),
92 ])
93 }
94}
95
96pub struct TaskTable {
98 tasks: Vec<Task>,
99 next_id: u64,
100 capacity: usize,
101}
102
103impl Default for TaskTable {
104 fn default() -> Self {
105 Self::new(128)
106 }
107}
108
109impl TaskTable {
110 pub fn new(capacity: usize) -> Self {
112 Self {
113 tasks: Vec::new(),
114 next_id: 1,
115 capacity: capacity.clamp(1, 1024),
116 }
117 }
118 pub fn create(&mut self, action: &str, cancellable: bool) -> Result<u64, ActionError> {
121 if self.tasks.len() >= self.capacity {
122 if let Some(oldest) = self.tasks.iter().position(|t| t.status.terminal()) {
123 self.tasks.remove(oldest);
124 }
125 }
126 if self.tasks.len() >= self.capacity || self.next_id == u64::MAX {
127 return Err(ActionError::new(
128 ErrorCode::LimitExceeded,
129 "Task table is full",
130 ));
131 }
132 if action.is_empty() || action.len() > 128 {
133 return Err(ActionError::new(
134 ErrorCode::InvalidInput,
135 "Invalid task action name",
136 ));
137 }
138 let id = self.next_id;
139 self.next_id += 1;
140 self.tasks.push(Task {
141 id,
142 action: action.into(),
143 status: Status::Pending,
144 progress: None,
145 message: String::new(),
146 cancellable,
147 result: None,
148 error: None,
149 });
150 Ok(id)
151 }
152 pub fn get(&self, id: u64) -> Result<&Task, ActionError> {
154 self.tasks.iter().find(|t| t.id == id).ok_or_else(not_found)
155 }
156 fn get_mut(&mut self, id: u64) -> Result<&mut Task, ActionError> {
157 self.tasks
158 .iter_mut()
159 .find(|t| t.id == id)
160 .ok_or_else(not_found)
161 }
162 pub fn request_cancel(&mut self, id: u64) -> Result<&Task, ActionError> {
164 let task = self.get_mut(id)?;
165 if task.status.terminal() {
166 return Ok(task);
167 }
168 if !task.cancellable {
169 return Err(ActionError::new(
170 ErrorCode::Unsupported,
171 "This task does not support cancellation",
172 ));
173 }
174 task.status = Status::Cancelling;
175 Ok(task)
176 }
177 pub fn checkpoint(&mut self, id: u64) -> Result<bool, ActionError> {
179 let task = self.get_mut(id)?;
180 if task.status == Status::Cancelling {
181 task.status = Status::Cancelled;
182 task.error = Some(ActionError::new(
183 ErrorCode::Cancelled,
184 "Task cancelled at an interruption point",
185 ));
186 return Ok(false);
187 }
188 if task.status.terminal() {
189 return Ok(false);
190 }
191 task.status = Status::Running;
192 Ok(true)
193 }
194 pub fn update(
196 &mut self,
197 id: u64,
198 progress: Option<u16>,
199 message: &str,
200 ) -> Result<(), ActionError> {
201 if progress.is_some_and(|p| p > 1000) || message.len() > 4096 {
202 return Err(ActionError::new(
203 ErrorCode::InvalidInput,
204 "Invalid task progress",
205 ));
206 }
207 let task = self.get_mut(id)?;
208 if task.status.terminal() {
209 return Err(ActionError::new(
210 ErrorCode::Unavailable,
211 "Task already finished",
212 ));
213 }
214 task.progress = progress;
215 task.message = message.into();
216 Ok(())
217 }
218 pub fn finish(&mut self, id: u64, result: ActionResult) -> Result<(), ActionError> {
220 let task = self.get_mut(id)?;
221 if task.status.terminal() || task.status == Status::Cancelling {
222 return Err(ActionError::new(
223 ErrorCode::Unavailable,
224 "Task is finished or awaiting cancellation",
225 ));
226 }
227 if result.task.is_some() {
228 return Err(ActionError::new(
229 ErrorCode::InvalidInput,
230 "Task completion cannot defer itself",
231 ));
232 }
233 task.status = Status::Complete;
234 task.progress = Some(1000);
235 task.result = Some(result);
236 Ok(())
237 }
238 pub fn fail(&mut self, id: u64, error: ActionError) -> Result<(), ActionError> {
240 let task = self.get_mut(id)?;
241 if task.status.terminal() {
242 return Err(ActionError::new(
243 ErrorCode::Unavailable,
244 "Task already finished",
245 ));
246 }
247 task.status = Status::Failed;
248 task.error = Some(error);
249 Ok(())
250 }
251 pub fn forget(&mut self, id: u64) -> Result<(), ActionError> {
253 let Some(index) = self.tasks.iter().position(|t| t.id == id) else {
254 return Err(not_found());
255 };
256 if !self.tasks[index].status.terminal() {
257 return Err(ActionError::new(
258 ErrorCode::Unavailable,
259 "Running tasks cannot be forgotten",
260 ));
261 }
262 self.tasks.remove(index);
263 Ok(())
264 }
265 pub fn list(&self, after: u64, limit: usize) -> Result<Value, ActionError> {
267 if limit == 0 || limit > 100 {
268 return Err(ActionError::new(
269 ErrorCode::InvalidInput,
270 "Task page limit is 1 to 100",
271 ));
272 }
273 let tasks: Vec<&Task> = self
274 .tasks
275 .iter()
276 .filter(|t| t.id > after)
277 .take(limit + 1)
278 .collect();
279 let cursor = if tasks.len() > limit {
280 Value::u64(tasks[limit - 1].id)
281 } else {
282 Value::Null
283 };
284 Ok(Value::object([
285 (
286 "tasks",
287 Value::Array(tasks.into_iter().take(limit).map(Task::to_json).collect()),
288 ),
289 ("nextCursor", cursor),
290 ]))
291 }
292}
293
294fn not_found() -> ActionError {
295 ActionError::new(ErrorCode::NotFound, "Task not found in this instance")
296}