use crate::machine::Machine;
use crate::{query, render, solve};
use std::io::{self, Write};
pub enum QueryResult {
Solutions,
ParseError(String),
RuntimeError(String),
}
pub fn run_query(m: &mut Machine, q: &str) -> QueryResult {
let goal = match query::parse_query(m, q) {
Ok(g) => g,
Err(e) => return QueryResult::ParseError(format!("Parse error: {e}")),
};
match solve::solve(m, goal) {
solve::Outcome::Error => {
let msg = m.error.take().map(|e| e.message).unwrap_or_default();
QueryResult::RuntimeError(format!("Runtime error: {msg}"))
}
solve::Outcome::Done => QueryResult::Solutions,
}
}
pub fn exhausted(m: &Machine) -> bool {
m.solution_limit.is_none_or(|l| m.solutions.len() < l)
}
pub fn write_error_json<W: Write>(w: &mut W, message: &str) -> io::Result<()> {
write!(w, "{{\"error\":\"{}\"}}", render::json_escape(message))
}
pub fn write_solutions_json<W: Write>(
w: &mut W,
m: &Machine,
exhausted: bool,
output: Option<&str>,
) -> io::Result<()> {
write!(
w,
"{{\"count\":{},\"exhausted\":{}",
m.solutions.len(),
exhausted
)?;
if let Some(out) = output {
write!(w, ",\"output\":\"{}\"", render::json_escape(out))?;
}
w.write_all(b",\"solutions\":[")?;
for (i, sol) in m.solutions.iter().enumerate() {
if i > 0 {
w.write_all(b",")?;
}
w.write_all(b"{")?;
for (j, (name, json, _)) in sol.bindings.iter().enumerate() {
if j > 0 {
w.write_all(b",")?;
}
write!(w, "\"{}\":{}", render::json_escape(name), json)?;
}
w.write_all(b"}")?;
}
w.write_all(b"]}")
}
#[cfg(test)]
mod tests {
use super::*;
use plg_shared::StringInterner;
fn machine() -> Box<Machine> {
Machine::new(StringInterner::new(), Vec::new())
}
fn bytes(f: impl FnOnce(&mut Vec<u8>) -> io::Result<()>) -> String {
let mut buf = Vec::new();
f(&mut buf).unwrap();
String::from_utf8(buf).unwrap()
}
#[test]
fn empty_success_matches_v1_shape() {
let m = machine();
assert_eq!(
bytes(|w| write_solutions_json(w, &m, true, None)),
"{\"count\":0,\"exhausted\":true,\"solutions\":[]}"
);
}
#[test]
fn output_field_sorts_between_exhausted_and_solutions() {
let m = machine();
assert_eq!(
bytes(|w| write_solutions_json(w, &m, false, Some("hi\n"))),
"{\"count\":0,\"exhausted\":false,\"output\":\"hi\\n\",\"solutions\":[]}"
);
}
#[test]
fn error_object_is_escaped() {
assert_eq!(
bytes(|w| write_error_json(w, "a\"b")),
"{\"error\":\"a\\\"b\"}"
);
}
#[test]
fn exhausted_follows_the_limit() {
let mut m = machine();
assert!(exhausted(&m), "no limit => exhausted");
m.solution_limit = Some(2);
assert!(exhausted(&m), "under the limit => exhausted");
m.solutions
.push(render::RenderedSolution { bindings: vec![] });
m.solutions
.push(render::RenderedSolution { bindings: vec![] });
assert!(!exhausted(&m), "limit hit exactly => not exhausted");
}
}