Skip to main content

koi_runtime/
http.rs

1//! Runtime adapter HTTP route handlers.
2//!
3//! Domain-owned routes mounted by the binary crate at `/v1/runtime/`.
4
5use std::sync::Arc;
6
7use axum::extract::Extension;
8use axum::http::StatusCode;
9use axum::response::IntoResponse;
10use axum::routing::get;
11use axum::{Json, Router};
12use serde::Serialize;
13use utoipa::ToSchema;
14
15use crate::instance::Instance;
16use crate::RuntimeCore;
17
18/// Route path constants.
19pub mod paths {
20    pub const PREFIX: &str = "/v1/runtime";
21    pub const STATUS: &str = "/v1/runtime/status";
22    pub const INSTANCES: &str = "/v1/runtime/instances";
23
24    pub fn rel(full: &str) -> &str {
25        full.strip_prefix(PREFIX).unwrap_or(full)
26    }
27}
28
29/// Build the runtime router with domain-owned routes.
30pub fn routes(core: Arc<RuntimeCore>) -> Router {
31    use paths::rel;
32    Router::new()
33        .route(rel(paths::STATUS), get(status_handler))
34        .route(rel(paths::INSTANCES), get(instances_handler))
35        .layer(Extension(core))
36}
37
38/// Runtime adapter status.
39#[derive(Debug, Serialize, ToSchema)]
40pub struct RuntimeStatus {
41    /// Whether the runtime adapter is active.
42    pub active: bool,
43    /// Backend name (docker, podman, systemd, etc.).
44    pub backend: Option<String>,
45    /// Number of tracked instances.
46    pub instance_count: usize,
47}
48
49/// GET /status — Runtime adapter status.
50#[utoipa::path(get, path = "/status", tag = "runtime",
51    summary = "Runtime adapter status",
52    responses((status = 200, body = RuntimeStatus)))]
53async fn status_handler(Extension(core): Extension<Arc<RuntimeCore>>) -> impl IntoResponse {
54    let status = core.status().await;
55    Json(status)
56}
57
58/// GET /instances — List all tracked instances.
59#[utoipa::path(get, path = "/instances", tag = "runtime",
60    summary = "List runtime-managed instances",
61    responses((status = 200, body = Vec<Instance>)))]
62async fn instances_handler(Extension(core): Extension<Arc<RuntimeCore>>) -> impl IntoResponse {
63    match core.list_instances().await {
64        Ok(instances) => (StatusCode::OK, Json(instances)).into_response(),
65        Err(e) => {
66            let code = koi_common::error::ErrorCode::from(&e);
67            let status = StatusCode::from_u16(code.http_status())
68                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
69            koi_common::http::error_response_with_status(status, code, e.to_string())
70        }
71    }
72}
73
74/// OpenAPI documentation for the runtime domain.
75#[derive(utoipa::OpenApi)]
76#[openapi(
77    paths(status_handler, instances_handler),
78    components(schemas(
79        RuntimeStatus,
80        Instance,
81        crate::instance::PortMapping,
82        crate::instance::PortProtocol,
83        crate::instance::InstanceState,
84        crate::instance::KoiMetadata,
85    ))
86)]
87pub struct RuntimeApiDoc;