hive-agent: replace json! with typed structs in web_ui handlers

This commit is contained in:
damocles 2026-08-13 19:21:05 +02:00 committed by mara
commit 786e4610f0
3 changed files with 34 additions and 15 deletions

View file

@ -7,7 +7,7 @@ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use serde::Deserialize; use serde::{Deserialize, Serialize};
use super::{AppState, SigintOutcome, error_response}; use super::{AppState, SigintOutcome, error_response};
@ -202,5 +202,10 @@ pub(super) async fn post_mark_todos_done(Form(form): Form<MarkTodosDoneForm>) ->
acked += count; acked += count;
} }
} }
axum::Json(serde_json::json!({ "acked": acked })).into_response() axum::Json(MarkTodosDoneBody { acked }).into_response()
}
#[derive(Serialize)]
struct MarkTodosDoneBody {
acked: u64,
} }

View file

@ -2,7 +2,7 @@
use axum::extract::State; use axum::extract::State;
use axum::response::{IntoResponse, Response}; use axum::response::{IntoResponse, Response};
use serde::Deserialize; use serde::{Deserialize, Serialize};
use super::AppState; use super::AppState;
@ -40,6 +40,11 @@ async fn fetch_reminder_stats(window_secs: u64) -> Option<hive_sh4re::approvals:
} }
} }
#[derive(Serialize)]
struct TodosBody {
todos: Vec<hive_sh4re::inbox::LooseEnd>,
}
/// `GET /api/todos` — snapshot of this agent's local todos (loose-ends v2). /// `GET /api/todos` — snapshot of this agent's local todos (loose-ends v2).
/// ///
/// Connects to the in-agent harness socket (`HIVE_AGENT_SOCKET`) and calls /// Connects to the in-agent harness socket (`HIVE_AGENT_SOCKET`) and calls
@ -54,5 +59,5 @@ pub(super) async fn api_todos() -> Response {
Some(hive_agent_sock::Response::LooseEnds { loose_ends }) => loose_ends, Some(hive_agent_sock::Response::LooseEnds { loose_ends }) => loose_ends,
_ => Vec::new(), _ => Vec::new(),
}; };
axum::Json(serde_json::json!({ "todos": todos })).into_response() axum::Json(TodosBody { todos }).into_response()
} }

View file

@ -5,11 +5,23 @@ use std::convert::Infallible;
use axum::Json; use axum::Json;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::response::sse::{Event, KeepAlive, Sse}; use axum::response::sse::{Event, KeepAlive, Sse};
use serde::Deserialize; use serde::{Deserialize, Serialize};
use tokio_stream::{Stream, StreamExt, wrappers::BroadcastStream}; use tokio_stream::{Stream, StreamExt, wrappers::BroadcastStream};
use super::AppState; use super::AppState;
/// Response body for `GET /api/events/history`. `seq` is omitted from the
/// wire entirely on a paginated (non-initial) load — matches the old
/// `json!` shape, which only ever set the `"seq"` key when `Some`.
#[derive(Serialize)]
pub(super) struct EventsHistoryBody {
events: Vec<crate::events::StoredEvent>,
min_id: Option<i64>,
has_more: bool,
#[serde(skip_serializing_if = "Option::is_none")]
seq: Option<u64>,
}
/// Query params for the paginated history endpoint. /// Query params for the paginated history endpoint.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub(super) struct HistoryParams { pub(super) struct HistoryParams {
@ -23,7 +35,7 @@ pub(super) struct HistoryParams {
pub(super) async fn events_history( pub(super) async fn events_history(
State(state): State<AppState>, State(state): State<AppState>,
Query(params): Query<HistoryParams>, Query(params): Query<HistoryParams>,
) -> Json<serde_json::Value> { ) -> Json<EventsHistoryBody> {
use crate::events::HISTORY_CAPACITY; use crate::events::HISTORY_CAPACITY;
let limit = params.limit.unwrap_or(100).min(HISTORY_CAPACITY); let limit = params.limit.unwrap_or(100).min(HISTORY_CAPACITY);
let before = params.before; let before = params.before;
@ -51,15 +63,12 @@ pub(super) async fn events_history(
se se
}) })
.collect(); .collect();
let mut resp = serde_json::json!({ Json(EventsHistoryBody {
"events": events, events,
"min_id": min_id, min_id,
"has_more": has_more, has_more,
}); seq,
if let Some(s) = seq { })
resp["seq"] = serde_json::json!(s);
}
Json(resp)
} }
pub(super) async fn events_stream( pub(super) async fn events_stream(