use std::io::Cursor;
use tiny_http::Response;
use crate::handlers::{error_response, json_response, State};
use lex_ast::{stage_id, Stage};
use lex_vcs::{Intent, IntentLog, Issue, IssueLog};
pub(crate) fn stages_batch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let stages: Vec<Stage> = match serde_json::from_str(body) {
Ok(s) => s,
Err(e) => return error_response(400, format!("body must be a JSON array of Stage: {e}")),
};
let store = state.store.lock().unwrap();
let ids: Vec<Option<String>> = stages.iter().map(stage_id).collect();
let known: Vec<String> = ids.iter().flatten().cloned().collect();
let mut present: std::collections::BTreeSet<String> = known
.iter()
.zip(store.get_asts_bulk(&known))
.filter(|(_, got)| got.is_ok())
.map(|(id, _)| id.clone())
.collect();
let (mut added, mut skipped) = (0usize, 0usize);
for (stage, id) in stages.iter().zip(ids) {
let id = match id {
Some(id) => id,
None => { skipped += 1; continue } };
let existed = !present.insert(id.clone());
if let Err(e) = store.publish(stage) {
return error_response(500, format!("publish stage {id}: {e}"));
}
if existed { skipped += 1 } else { added += 1 }
}
json_response(200, &serde_json::json!({
"received": stages.len(), "added": added, "skipped": skipped,
}))
}
pub(crate) fn stages_fetch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let ids = match parse_ids(body) {
Ok(ids) => ids,
Err(resp) => return resp,
};
let store = state.store.lock().unwrap();
let stages: Vec<Stage> = store.get_asts_bulk(&ids).into_iter().filter_map(Result::ok).collect();
json_response(200, &serde_json::json!({ "stages": stages }))
}
pub(crate) fn stages_missing_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let v: serde_json::Value = match serde_json::from_str(body) {
Ok(v) => v,
Err(e) => return error_response(400, format!("body must be JSON: {e}")),
};
let store = state.store.lock().unwrap();
if let Some(arr) = v.get("pairs").and_then(|p| p.as_array()) {
let pairs: Vec<(String, String)> = arr
.iter()
.filter_map(|p| {
let a = p.as_array()?;
Some((a.first()?.as_str()?.to_string(), a.get(1)?.as_str()?.to_string()))
})
.collect();
let have = store.get_asts_for_sigs_bulk(&pairs);
let missing: Vec<serde_json::Value> = pairs
.iter()
.zip(have)
.filter(|(_, got)| got.is_err())
.map(|((sig, stage), _)| serde_json::json!([sig, stage]))
.collect();
return json_response(200, &serde_json::json!({ "missing": missing }));
}
let ids = match parse_ids(body) {
Ok(ids) => ids,
Err(resp) => return resp,
};
let have = store.get_asts_bulk(&ids);
let missing: Vec<String> = ids
.into_iter()
.zip(have)
.filter(|(_, got)| got.is_err())
.map(|(id, _)| id)
.collect();
json_response(200, &serde_json::json!({ "missing": missing }))
}
pub(crate) fn intents_batch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let intents: Vec<Intent> = match serde_json::from_str(body) {
Ok(i) => i,
Err(e) => return error_response(400, format!("body must be a JSON array of Intent: {e}")),
};
let store = state.store.lock().unwrap();
let log = match IntentLog::open(store.root()) {
Ok(l) => l,
Err(e) => return error_response(500, format!("opening intent log: {e}")),
};
let mut added = 0usize;
for intent in &intents {
let existed = matches!(log.get(&intent.intent_id), Ok(Some(_)));
if let Err(e) = log.put(intent) {
return error_response(500, format!("put intent {}: {e}", intent.intent_id));
}
if !existed { added += 1 }
}
json_response(200, &serde_json::json!({
"received": intents.len(), "added": added,
}))
}
pub(crate) fn intents_fetch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let ids = match parse_ids(body) {
Ok(ids) => ids,
Err(resp) => return resp,
};
let store = state.store.lock().unwrap();
let log = match IntentLog::open(store.root()) {
Ok(l) => l,
Err(e) => return error_response(500, format!("opening intent log: {e}")),
};
let intents: Vec<Intent> = ids.iter().filter_map(|id| log.get(id).ok().flatten()).collect();
json_response(200, &serde_json::json!({ "intents": intents }))
}
pub(crate) fn locks_batch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let v: serde_json::Value = match serde_json::from_str(body) {
Ok(v) => v,
Err(e) => return error_response(400, format!("body must be JSON: {e}")),
};
let entries = match v.as_array() {
Some(a) => a,
None => return error_response(400, "body must be a JSON array of {head_op, lock}"),
};
let store = state.store.lock().unwrap();
let mut added = 0usize;
for e in entries {
let head_op = match e.get("head_op").and_then(|x| x.as_str()) {
Some(h) => h,
None => return error_response(400, "each entry needs a string `head_op`"),
};
let lock = match e.get("lock").and_then(|x| x.as_str()) {
Some(l) => l,
None => return error_response(400, "each entry needs a string `lock`"),
};
if let Err(err) = store.set_committed_lock(head_op, lock) {
return error_response(500, format!("store lock for {head_op}: {err}"));
}
added += 1;
}
json_response(200, &serde_json::json!({ "received": entries.len(), "added": added }))
}
pub(crate) fn locks_fetch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let v: serde_json::Value = match serde_json::from_str(body) {
Ok(v) => v,
Err(e) => return error_response(400, format!("body must be JSON: {e}")),
};
let ids = match v.get("head_ops").and_then(|i| i.as_array()) {
Some(a) => a,
None => return error_response(400, "missing array field `head_ops`"),
};
let store = state.store.lock().unwrap();
let mut locks = serde_json::Map::new();
for id in ids.iter().filter_map(|x| x.as_str()) {
match store.committed_lock(id) {
Ok(Some(toml)) => {
locks.insert(id.to_string(), serde_json::Value::String(toml));
}
Ok(None) => {}
Err(e) => return error_response(500, format!("read lock {id}: {e}")),
}
}
json_response(200, &serde_json::json!({ "locks": locks }))
}
pub(crate) fn issues_batch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let issues: Vec<Issue> = match serde_json::from_str(body) {
Ok(i) => i,
Err(e) => return error_response(400, format!("body must be a JSON array of Issue: {e}")),
};
let store = state.store.lock().unwrap();
let log = match IssueLog::open(store.root()) {
Ok(l) => l,
Err(e) => return error_response(500, format!("opening issue log: {e}")),
};
if let Some(bad) = issues.iter().find(|i| !i.id_is_consistent()) {
return error_response(
400,
format!(
"issue {}: issue_id does not match its content (expected {})",
bad.issue_id,
bad.computed_id()
),
);
}
let mut added = 0usize;
for issue in &issues {
let existed = matches!(log.get(&issue.issue_id), Ok(Some(_)));
if let Err(e) = log.put(issue) {
return error_response(500, format!("put issue {}: {e}", issue.issue_id));
}
if !existed { added += 1 }
}
json_response(200, &serde_json::json!({
"received": issues.len(), "added": added,
}))
}
pub(crate) fn issues_fetch_handler(state: &State, body: &str) -> Response<Cursor<Vec<u8>>> {
let ids = match parse_ids(body) {
Ok(ids) => ids,
Err(resp) => return resp,
};
let store = state.store.lock().unwrap();
let log = match IssueLog::open(store.root()) {
Ok(l) => l,
Err(e) => return error_response(500, format!("opening issue log: {e}")),
};
let issues: Vec<Issue> = ids.iter().filter_map(|id| log.get(id).ok().flatten()).collect();
json_response(200, &serde_json::json!({ "issues": issues }))
}
pub(crate) fn issues_list_handler(state: &State) -> Response<Cursor<Vec<u8>>> {
let store = state.store.lock().unwrap();
let log = match IssueLog::open(store.root()) {
Ok(l) => l,
Err(e) => return error_response(500, format!("opening issue log: {e}")),
};
match log.list_ids() {
Ok(ids) => json_response(200, &serde_json::json!({ "ids": ids })),
Err(e) => error_response(500, format!("listing issues: {e}")),
}
}
fn parse_ids(body: &str) -> Result<Vec<String>, Response<Cursor<Vec<u8>>>> {
let v: serde_json::Value = serde_json::from_str(body)
.map_err(|e| error_response(400, format!("body must be JSON: {e}")))?;
let ids = v.get("ids").and_then(|i| i.as_array())
.ok_or_else(|| error_response(400, "missing array field `ids`"))?;
Ok(ids.iter().filter_map(|x| x.as_str().map(String::from)).collect())
}