use axum::extract::{Path, Query, State};
use axum::http::HeaderMap;
use axum::Json;
use serde::Deserialize;
use crate::auth::require_bearer;
use crate::error::{AppError, AppErrorKind};
use crate::state::{AppState, CellRecord, CellState};
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ListCellsParams {
pub state: Option<String>,
pub formation: Option<String>,
pub limit: Option<usize>,
}
const MAX_LIMIT: usize = 1000;
pub async fn list_cells(
State(state): State<AppState>,
headers: HeaderMap,
Query(params): Query<ListCellsParams>,
) -> Result<Json<Vec<CellRecord>>, AppError> {
require_bearer(&headers, &state.api_token)?;
let state_filter = match params.state.as_deref() {
None => None,
Some(raw) => Some(parse_state_filter(raw)?),
};
let limit = params.limit.map(|n| n.min(MAX_LIMIT));
let map = state.cells.read().await;
let mut out: Vec<CellRecord> = map
.values()
.filter(|c| match state_filter {
Some(want) => c.state == want,
None => true,
})
.filter(|c| match params.formation.as_deref() {
None => true,
Some(want) => c
.formation_id
.as_ref()
.map(|fid| fid.to_string() == want)
.unwrap_or(false),
})
.cloned()
.collect();
if let Some(n) = limit {
out.truncate(n);
}
Ok(Json(out))
}
pub async fn get_cell(
State(state): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
) -> Result<Json<CellRecord>, AppError> {
require_bearer(&headers, &state.api_token)?;
let map = state.cells.read().await;
map.get(&id)
.cloned()
.map(Json)
.ok_or_else(|| AppError::not_found(format!("cell {id} not found")))
}
fn parse_state_filter(raw: &str) -> Result<CellState, AppError> {
match raw.to_ascii_lowercase().as_str() {
"pending" => Ok(CellState::Pending),
"running" => Ok(CellState::Running),
"destroyed" => Ok(CellState::Destroyed),
other => Err(AppError::new(
AppErrorKind::BadRequest,
format!("unknown ?state value: {other} (want pending|running|destroyed)"),
)),
}
}