use std::sync::Arc;
use axum::Json;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use super::super::types::{CreateBoxRequest, ListBoxesResponse, RemoveQuery};
use super::super::{
AppState, build_box_options, error_from_boxlite, error_response, get_or_fetch_box,
};
pub(in crate::commands::serve) async fn create_box(
State(state): State<Arc<AppState>>,
Json(req): Json<CreateBoxRequest>,
) -> Response {
let name = req.name.clone();
let requested = boxlite::BoxLifecyclePolicy {
auto_stop: req.auto_stop.unwrap_or(0),
auto_delete: req.auto_delete.unwrap_or(0),
auto_resume: req.auto_resume.unwrap_or(true),
};
if let Err(e) = requested.validate() {
return error_response(
StatusCode::BAD_REQUEST,
e.to_string(),
"InvalidArgumentError",
"invalid_argument",
);
}
let options = match build_box_options(&req) {
Ok(options) => options,
Err(e) => {
return error_response(
StatusCode::BAD_REQUEST,
e.to_string(),
"InvalidArgumentError",
"invalid_argument",
);
}
};
let litebox = match state.runtime.create(options, name).await {
Ok(b) => b,
Err(e) => return error_from_boxlite(&e),
};
let info = match litebox.info().await {
Ok(info) => info,
Err(e) => return error_from_boxlite(&e),
};
let box_id = info.id.to_string();
state
.set_lifecycle(
&box_id,
crate::commands::serve::LifecyclePolicy {
auto_stop: requested.auto_stop,
auto_delete: requested.auto_delete,
},
)
.await;
let resp = state.box_response(&info).await;
state.boxes.write().await.insert(box_id, Arc::new(litebox));
(StatusCode::CREATED, Json(resp)).into_response()
}
pub(in crate::commands::serve) async fn list_boxes(State(state): State<Arc<AppState>>) -> Response {
match state.runtime.list_info().await {
Ok(infos) => {
let mut boxes = Vec::with_capacity(infos.len());
for info in &infos {
boxes.push(state.box_response(info).await);
}
Json(ListBoxesResponse { boxes }).into_response()
}
Err(e) => error_from_boxlite(&e),
}
}
pub(in crate::commands::serve) async fn get_box(
State(state): State<Arc<AppState>>,
Path(box_id): Path<String>,
) -> Response {
match state.runtime.get_info(&box_id).await {
Ok(Some(info)) => Json(state.box_response(&info).await).into_response(),
Ok(None) => error_response(
StatusCode::NOT_FOUND,
format!("box not found: {box_id}"),
"NotFoundError",
"not_found",
),
Err(e) => error_from_boxlite(&e),
}
}
pub(in crate::commands::serve) async fn head_box(
State(state): State<Arc<AppState>>,
Path(box_id): Path<String>,
) -> Response {
match state.runtime.exists(&box_id).await {
Ok(true) => StatusCode::NO_CONTENT.into_response(),
Ok(false) => StatusCode::NOT_FOUND.into_response(),
Err(e) => error_from_boxlite(&e),
}
}
pub(in crate::commands::serve) async fn start_box(
State(state): State<Arc<AppState>>,
Path(box_id): Path<String>,
) -> Response {
let cached = state.boxes.read().await.get(&box_id).cloned();
if let Some(cached) = cached {
let info = match cached.info().await {
Ok(info) => info,
Err(e) => return error_from_boxlite(&e),
};
if !info.status.is_active() {
let mut boxes = state.boxes.write().await;
if boxes
.get(&box_id)
.is_some_and(|current| Arc::ptr_eq(current, &cached))
{
boxes.remove(&box_id);
}
}
}
let litebox = match get_or_fetch_box(&state, &box_id).await {
Ok(b) => b,
Err(resp) => return resp,
};
if let Err(e) = litebox.start().await {
return error_from_boxlite(&e);
}
let info = match litebox.info().await {
Ok(info) => info,
Err(e) => return error_from_boxlite(&e),
};
Json(state.box_response(&info).await).into_response()
}
pub(in crate::commands::serve) async fn stop_box(
State(state): State<Arc<AppState>>,
Path(box_id): Path<String>,
) -> Response {
let litebox = match get_or_fetch_box(&state, &box_id).await {
Ok(b) => b,
Err(resp) => return resp,
};
if let Err(e) = litebox.stop().await {
return error_from_boxlite(&e);
}
state.boxes.write().await.remove(&box_id);
let info = match litebox.info().await {
Ok(info) => info,
Err(e) => return error_from_boxlite(&e),
};
Json(state.box_response(&info).await).into_response()
}
pub(in crate::commands::serve) async fn remove_box(
State(state): State<Arc<AppState>>,
Path(box_id): Path<String>,
Query(query): Query<RemoveQuery>,
) -> Response {
state.boxes.write().await.remove(&box_id);
let force = query.force.unwrap_or(true);
let canonical = match state.runtime.get_info(&box_id).await {
Ok(info) => info.map(|info| info.id.to_string()),
Err(error) => {
tracing::warn!(
box_id = %box_id,
%error,
"could not resolve box id before removal; a deadline filed under \
another spelling will be left behind"
);
None
}
};
match state.runtime.remove(&box_id, force).await {
Ok(()) => {
state.forget_box(&box_id).await;
if let Some(canonical) = canonical.filter(|id| id != &box_id) {
state.forget_box(&canonical).await;
}
StatusCode::NO_CONTENT.into_response()
}
Err(e) => error_from_boxlite(&e),
}
}