solti_api/handler.rs
1//! # Handler trait.
2//!
3//! [`ApiHandler`] defines the transport-agnostic API surface.
4//! Implement this trait to plug custom logic (auth, rate limiting, metrics) between the wire layer and the supervisor.
5
6use std::pin::Pin;
7
8use async_trait::async_trait;
9use solti_model::{OutputEvent, Task, TaskId, TaskPage, TaskQuery, TaskRun, TaskSpec};
10use tokio_stream::Stream;
11
12use crate::error::ApiError;
13
14/// Boxed stream of [`OutputEvent`]s — the wire-side surface of live task logs.
15pub type OutputEventStream = Pin<Box<dyn Stream<Item = OutputEvent> + Send + 'static>>;
16
17/// Task execution API handler.
18///
19/// ## Also
20///
21/// - [`SupervisorApiAdapter`](crate::SupervisorApiAdapter) ready-to-use implementation.
22/// - [`ApiError`](crate::ApiError) error type returned by all methods.
23///
24/// This trait abstracts the backend implementation, allowing users to:
25/// - Use the provided [`SupervisorApiAdapter`](crate::SupervisorApiAdapter)
26/// - Implement custom handlers with additional logic (auth, rate limiting, etc.)
27///
28/// ## API surface
29///
30/// | Method | HTTP | gRPC |
31/// |--------------------|-----------------------------------|---------------------|
32/// | `submit_task` | `POST /api/v1/tasks` | `SubmitTask` |
33/// | `get_task_status` | `GET /api/v1/tasks/{id}` | `GetTaskStatus` |
34/// | `query_tasks` | `GET /api/v1/tasks` | `ListTasks` |
35/// | `list_task_runs` | `GET /api/v1/tasks/{id}/runs` | `ListTaskRuns` |
36/// | `delete_task` | `DELETE /api/v1/tasks/{id}` | `DeleteTask` |
37/// | `stream_task_logs` | `GET /api/v1/tasks/{id}/logs` | `StreamTaskLogs` |
38#[async_trait]
39pub trait ApiHandler: Send + Sync + 'static {
40 /// Submit a new task for execution.
41 async fn submit_task(&self, spec: TaskSpec) -> Result<TaskId, ApiError>;
42
43 /// Get current status of a task by ID.
44 async fn get_task_status(&self, id: &TaskId) -> Result<Option<Task>, ApiError>;
45
46 /// Query tasks with combined filters and pagination.
47 ///
48 /// Supports filtering by slot and/or status simultaneously, with offset/limit pagination. Returns a page with total count.
49 async fn query_tasks(&self, query: TaskQuery) -> Result<TaskPage<Task>, ApiError>;
50
51 /// List execution history for a specific task (oldest first).
52 async fn list_task_runs(&self, id: &TaskId) -> Result<Vec<TaskRun>, ApiError>;
53
54 /// Stop a task and purge its run history.
55 ///
56 /// Idempotent:
57 /// returns `Ok(())` whether the task is currently registered on the agent.
58 /// Errors only on supervisor cancellation failures (timeout, internal error).
59 async fn delete_task(&self, id: &TaskId) -> Result<(), ApiError>;
60
61 /// Subscribe to the live-tail stream of stdout/stderr lines for a task.
62 ///
63 /// Returns an [`OutputEventStream`] that yields [`OutputEvent`]s in real time.
64 /// The stream covers all subsequent runs of the task (multi-run merge) and ends when the task is fully terminal and evicted.
65 async fn stream_task_logs(&self, _id: &TaskId) -> Result<OutputEventStream, ApiError> {
66 Ok(Box::pin(tokio_stream::empty()))
67 }
68}