use std::sync::Arc;
use cratefield_core::{Database, Problem, ProblemDef, Scope, Tenancy};
use http::{HeaderMap, StatusCode};
use serde::Deserialize;
use serde_json::{Map, Value, json};
use crate::read::Tables;
pub const PATH: &str = "/__batch";
pub const MAX_READS: usize = 20;
pub const TOO_MANY_READS: ProblemDef = ProblemDef {
slug: "too-many-reads",
status: StatusCode::BAD_REQUEST,
title: "Too many reads in one batch",
description: "A batch carries at most 20 reads; send the rest as another batch.",
};
pub const NO_READS: ProblemDef = ProblemDef {
slug: "no-reads",
status: StatusCode::BAD_REQUEST,
title: "A batch with no reads",
description: "A batch names at least one read; an empty one is a request with no question.",
};
#[derive(Debug, Deserialize)]
pub struct Read {
pub table: String,
#[serde(default)]
pub after: Option<Value>,
#[serde(default)]
pub sort: Option<String>,
#[serde(default, rename = "where")]
pub filters: Map<String, Value>,
}
#[derive(Debug, Deserialize)]
pub struct Batch {
pub reads: Vec<Read>,
}
pub async fn run(
tables: &Tables,
conn: &dyn Database,
tenancy: Tenancy,
headers: &HeaderMap,
scope: &Scope,
batch: &Batch,
) -> Result<Value, Problem> {
if batch.reads.is_empty() {
return Err(Problem::new(&NO_READS));
}
if batch.reads.len() > MAX_READS {
return Err(Problem::new(&TOO_MANY_READS).with_detail(format!(
"{} reads were asked for and {MAX_READS} is the most",
batch.reads.len()
)));
}
let mut results = Vec::with_capacity(batch.reads.len());
for (at, read) in batch.reads.iter().enumerate() {
let filters = filters_of(read);
let body = crate::read::page(
tables,
conn,
tenancy,
headers,
scope,
crate::read::Asked {
table: &read.table,
after: read.after.as_ref(),
filters: &filters,
sort: crate::routes::sort_of(read.sort.as_deref()),
},
)
.await
.map_err(|problem| {
let said = problem.detail.clone();
problem.with_detail(match said {
Some(detail) => format!("read {at} (`{}`): {detail}", read.table),
None => format!("read {at} (`{}`)", read.table),
})
})?;
results.push(json!({ "table": read.table, "rows": body["rows"], "next": body["next"] }));
}
Ok(json!({ "results": results }))
}
fn filters_of(read: &Read) -> Vec<cratefield_tables::Filter> {
read.filters
.iter()
.map(|(column, value)| cratefield_tables::Filter {
column: column.clone(),
value: value.clone(),
})
.collect()
}
pub type State = Arc<Tables>;