hive-c0re: wire up openapi spec + swagger ui (#2872)

This commit is contained in:
damocles 2026-07-31 21:39:46 +02:00 committed by mara
commit 44651544a8
7 changed files with 256 additions and 8 deletions

View file

@ -12,10 +12,35 @@ use axum::{
response::{IntoResponse, Response},
routing::{get, post},
};
use utoipa::OpenApi;
use utoipa_axum::{router::OpenApiRouter, routes};
use utoipa_swagger_ui::SwaggerUi;
use crate::coordinator::Coordinator;
use crate::lifecycle;
/// Root of the auto-generated `OpenAPI` spec (`/api/openapi.json`, browsable
/// at `/api/docs`) — see [`utoipa`]. Only routes carrying a
/// `#[utoipa::path(...)]` annotation show up; the rest of the (much
/// larger) route table below is undocumented for now. Deliberately
/// incremental: an unannotated route just doesn't appear in the spec,
/// nothing breaks, so routes get annotated as a series of small
/// follow-ups rather than one mega-diff.
#[derive(OpenApi)]
#[openapi(
info(
title = "hyperhive dashboard API",
description = "hive-c0re's HTTP surface, served on the loopback \
dashboard port behind the gateway's /api/ + \
/health/ proxy prefixes."
),
tags(
(name = "health", description = "hive-wide liveness/readiness probes"),
(name = "journal", description = "container + host journal reads"),
)
)]
struct ApiDoc;
mod approvals;
mod build_logs;
mod extra_forges;
@ -79,12 +104,11 @@ pub async fn serve(
) -> Result<()> {
// API-only: the gateway static-serves the dashboard dist and proxies
// non-static requests here (see hive-gateway.nix). Unmatched paths 404.
// The four `#[utoipa::path]`-annotated routes are registered further
// down via `OpenApiRouter` instead of the plain `.route(...)` calls
// below — see the `router`/`api` split at the end of this fn.
let app = Router::new()
.route("/health/live", get(health::get_health_live))
.route("/health/ready", get(health::get_health_ready))
.route("/api/state", get(state_snapshot::api_state))
.route("/api/journal/{name}", get(journal::get_journal))
.route("/api/journal-host", get(journal::get_journal_host))
.route("/api/state-file", get(state_files::get_state_file))
.route(
"/api/matrix-accounts",
@ -239,6 +263,19 @@ pub async fn serve(
get(state_snapshot::dashboard_history),
)
// No static fallback — the gateway owns the dist; unmatched paths 404.
;
let (router, api) = OpenApiRouter::<AppState>::with_openapi(ApiDoc::openapi())
.routes(routes!(
health::get_health_live,
health::get_health_ready,
journal::get_journal,
journal::get_journal_host,
))
.merge(app.into())
.split_for_parts();
let app = router
.merge(SwaggerUi::new("/api/docs").url("/api/openapi.json", api))
.with_state(AppState {
coord,
webhook_secret,