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::{
10    AdmissionPolicy, OutputEvent, Task, TaskId, TaskPage, TaskQuery, TaskRun, TaskSpec,
11};
12use tokio_stream::Stream;
13
14use crate::error::ApiError;
15
16/// Boxed stream of [`OutputEvent`]s — the wire-side surface of live task logs.
17pub type OutputEventStream = Pin<Box<dyn Stream<Item = OutputEvent> + Send + 'static>>;
18
19/// Task execution API handler.
20///
21/// ## Also
22///
23/// - [`SupervisorApiAdapter`](crate::SupervisorApiAdapter) ready-to-use implementation.
24/// - [`ApiError`](crate::ApiError) error type returned by all methods.
25///
26/// This trait abstracts the backend implementation, allowing users to:
27/// - Use the provided [`SupervisorApiAdapter`](crate::SupervisorApiAdapter)
28/// - Implement custom handlers with additional logic (auth, rate limiting, etc.)
29///
30/// ## API surface
31///
32/// | Method             | HTTP                              | gRPC                |
33/// |--------------------|-----------------------------------|---------------------|
34/// | `submit_task`      | `POST   /api/v1/tasks`            | `SubmitTask`        |
35/// | `apply_task`       | `PUT    /api/v1/tasks`            | `ApplyTask`         |
36/// | `get_task_status`  | `GET    /api/v1/tasks/{id}`       | `GetTaskStatus`     |
37/// | `query_tasks`      | `GET    /api/v1/tasks`            | `ListTasks`         |
38/// | `list_task_runs`   | `GET    /api/v1/tasks/{id}/runs`  | `ListTaskRuns`      |
39/// | `delete_task`      | `DELETE /api/v1/tasks/{id}`       | `DeleteTask`        |
40/// | `stream_task_logs` | `GET    /api/v1/tasks/{id}/logs`  | `StreamTaskLogs`    |
41#[async_trait]
42pub trait ApiHandler: Send + Sync + 'static {
43    /// Submit a new task for execution.
44    async fn submit_task(&self, spec: TaskSpec) -> Result<TaskId, ApiError>;
45
46    /// Apply a spec to its slot (declarative upsert).
47    /// Returns the id of the task running in the slot after apply.
48    ///
49    /// Note: this **forces** [`AdmissionPolicy::Replace`], overriding any admission
50    /// policy on the supplied `spec` — that is the point of "apply" (latest spec
51    /// wins the slot). Use [`submit_task`](Self::submit_task) to honor the spec's
52    /// own admission policy.
53    async fn apply_task(&self, spec: TaskSpec) -> Result<TaskId, ApiError> {
54        self.submit_task(spec.with_admission(AdmissionPolicy::Replace))
55            .await
56    }
57
58    /// Get current status of a task by ID.
59    async fn get_task_status(&self, id: &TaskId) -> Result<Option<Task>, ApiError>;
60
61    /// Query tasks with combined filters and pagination.
62    ///
63    /// Supports filtering by slot and/or status simultaneously, with offset/limit pagination. Returns a page with total count.
64    async fn query_tasks(&self, query: TaskQuery) -> Result<TaskPage<Task>, ApiError>;
65
66    /// List execution history for a specific task (oldest first).
67    async fn list_task_runs(&self, id: &TaskId) -> Result<Vec<TaskRun>, ApiError>;
68
69    /// Stop a task and purge its run history.
70    ///
71    /// Idempotent:
72    /// returns `Ok(())` whether the task is currently registered on the agent.
73    /// Errors only on supervisor cancellation failures (timeout, internal error).
74    async fn delete_task(&self, id: &TaskId) -> Result<(), ApiError>;
75
76    /// Subscribe to the live-tail stream of stdout/stderr lines for a task.
77    ///
78    /// Returns an [`OutputEventStream`] that yields [`OutputEvent`]s in real time.
79    /// The stream covers all subsequent runs of the task (multi-run merge) and ends when the task is fully terminal and evicted.
80    async fn stream_task_logs(&self, _id: &TaskId) -> Result<OutputEventStream, ApiError> {
81        Ok(Box::pin(tokio_stream::empty()))
82    }
83}