Skip to main content

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}