use wsb::prelude::*;
use axum::{
extract::{Query, State},
http::StatusCode,
Json,
};
use super::sessions::ErrorResponse;
use crate::web::ops::OpKind;
use crate::web::state::WebState;
fn failed(error: impl std::fmt::Display) -> (StatusCode, Json<ErrorResponse>) {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: error.to_string(),
}),
)
}
#[derive(Deserialize)]
pub struct ListQuery {
pub offset: Option<i64>,
pub limit: Option<i64>,
}
pub async fn list_tests(
State(state): State<WebState>,
Query(query): Query<ListQuery>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
let db = state.db.clone();
let (tests, total) = tokio::task::spawn_blocking(move || {
let catalogue = db.catalogue_repo();
let conn = <_ as Repository<
wsb::persist::local::models::catalogued_test::CataloguedTest,
>>::get_connection(&*catalogue)?;
let tests = catalogue.list_impl(&conn, query.offset, query.limit)?;
let total = catalogue.count_impl(&conn)?;
anyhow::Ok((tests, total))
})
.await
.unwrap_or_else(|e| Err(anyhow!("join error: {}", e)))
.map_err(failed)?;
Ok(Json(serde_json::json!({ "tests": tests, "total": total })))
}
pub async fn survey(
State(state): State<WebState>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
let db = state.db.clone();
let survey = tokio::task::spawn_blocking(move || {
wsb::catalogue::report::Survey::read(&db.catalogue_repo())
})
.await
.unwrap_or_else(|e| Err(anyhow!("join error: {}", e)))
.map_err(failed)?;
Ok(Json(serde_json::json!({
"total": survey.total,
"numbered": survey.numbered,
"clean": survey.is_clean(),
"per_repo": survey
.per_repo
.iter()
.map(|(repo, all, numbered)| serde_json::json!({
"repo": repo,
"tests": all,
"numbered": numbered,
}))
.collect::<Vec<_>>(),
"duplicates": survey
.duplicates
.iter()
.map(|(repo, number, members)| serde_json::json!({
"repo": repo,
"number": number,
"members": members,
}))
.collect::<Vec<_>>(),
"misnumbered": survey
.misnumbered
.iter()
.map(|test| serde_json::json!({
"test": test,
"described_number": test.described_number(),
}))
.collect::<Vec<_>>(),
})))
}
#[derive(Deserialize)]
pub struct ParityQuery {
pub family: String,
}
pub async fn parity(
State(state): State<WebState>,
Query(query): Query<ParityQuery>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
let db = state.db.clone();
let root = state.workspace_root.clone();
let (members, gaps, complete) = tokio::task::spawn_blocking(move || {
let members = wsb::catalogue::families::members(&root, &query.family)?;
let parity = wsb::catalogue::report::Parity::read(&db.catalogue_repo(), &members)?;
let gaps = parity.gaps();
let complete = parity.rows.len() - gaps.len();
anyhow::Ok((members, gaps, complete))
})
.await
.unwrap_or_else(|e| Err(anyhow!("join error: {}", e)))
.map_err(failed)?;
Ok(Json(serde_json::json!({
"members": members,
"complete": complete,
"gaps": gaps
.into_iter()
.map(|(number, missing)| serde_json::json!({
"number": number,
"missing_from": missing,
}))
.collect::<Vec<_>>(),
})))
}
#[derive(Deserialize)]
pub struct FreeQuery {
pub from: Option<i64>,
pub count: Option<usize>,
}
pub async fn free_numbers(
State(state): State<WebState>,
Query(query): Query<FreeQuery>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
let db = state.db.clone();
let from = query.from.unwrap_or(1);
let count = query.count.unwrap_or(5).min(100);
let numbers = tokio::task::spawn_blocking(move || db.catalogue_repo().free_numbers(from, count))
.await
.unwrap_or_else(|e| Err(anyhow!("join error: {}", e)))
.map_err(failed)?;
Ok(Json(serde_json::json!({ "numbers": numbers })))
}
pub async fn scan(
State(state): State<WebState>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
let handle = state
.ops
.start_op(OpKind::CatalogueScan, "scanning the workspace for tests")
.await;
let id = handle.id().to_string();
let db = state.db.clone();
let root = state.workspace_root.clone();
tokio::spawn(async move {
let outcome = tokio::task::spawn_blocking(move || {
wsb::catalogue::scan::scan_workspace(&root, &db.catalogue_repo())
})
.await;
match outcome {
Ok(Ok(_)) => handle.complete().await,
Ok(Err(message)) => handle.fail(message).await,
Err(join) => handle.fail(format!("the scan task did not finish: {join}")).await,
}
});
Ok(Json(serde_json::json!({ "op_id": id })))
}