use sim_cookbook::{RecipeCard, RecipeRun, RecipeStore, ordered_cards, search, view};
pub(crate) fn render_error_json(message: &str) -> String {
let mut body = String::from("{\"error\":");
push_json_str(&mut body, message);
body.push('}');
body
}
pub(crate) fn render_index_json(store: &RecipeStore) -> String {
let mut out = String::from("{\"books\":[");
let cookbook = view(store);
for (book_index, book) in cookbook.books.iter().enumerate() {
comma(&mut out, book_index);
out.push_str("{\"id\":");
push_json_str(&mut out, &book.id);
out.push_str(",\"title\":");
push_json_str(&mut out, &book.title);
out.push_str(",\"chapters\":[");
for (chapter_index, chapter) in book.chapters.iter().enumerate() {
comma(&mut out, chapter_index);
out.push_str("{\"name\":");
push_json_str(&mut out, &chapter.name);
out.push_str(",\"title\":");
push_json_str(&mut out, &chapter.title);
out.push_str(",\"recipes\":[");
for (recipe_index, recipe) in chapter.recipes.iter().enumerate() {
comma(&mut out, recipe_index);
push_recipe_summary(&mut out, recipe);
}
out.push_str("]}");
}
out.push_str("]}");
}
out.push_str("],\"recipes\":[");
for (index, card) in ordered_cards(store).iter().enumerate() {
comma(&mut out, index);
push_recipe_summary(&mut out, card);
}
out.push_str("]}");
out
}
pub(crate) fn render_search_json(store: &RecipeStore, query: &str) -> String {
let mut out = String::from("{\"query\":");
push_json_str(&mut out, query);
out.push_str(",\"recipes\":[");
for (index, card) in search(store, query).iter().enumerate() {
comma(&mut out, index);
push_recipe_summary(&mut out, card);
}
out.push_str("]}");
out
}
pub(crate) fn render_recipe_json(store: &RecipeStore, card: &RecipeCard) -> String {
let mut out = String::from("{\"id\":");
push_json_str(&mut out, &card.id);
out.push_str(",\"title\":");
push_json_str(&mut out, &card.title);
out.push_str(",\"book\":");
push_json_str(&mut out, &card.book);
out.push_str(",\"chapter\":");
push_json_str(&mut out, &card.chapter);
out.push_str(",\"codec\":");
push_json_str(&mut out, &card.codec);
out.push_str(",\"purpose\":");
push_json_str(&mut out, &card.purpose);
out.push_str(",\"setup\":");
push_json_str(&mut out, &String::from_utf8_lossy(&card.setup));
out.push_str(",\"requires\":");
push_json_array(&mut out, &card.requires);
out.push_str(",\"tags\":");
push_json_array(&mut out, &card.tags);
out.push_str(",\"next\":");
if let Some(next) = sim_cookbook::next(store, &card.id) {
push_json_str(&mut out, &next.id);
} else {
out.push_str("null");
}
out.push('}');
out
}
pub(crate) fn render_run_json(run: &RecipeRun) -> String {
let mut out = String::from("{\"recipe\":");
push_json_str(&mut out, &run.recipe);
out.push_str(",\"ok\":");
out.push_str(if run.ok { "true" } else { "false" });
out.push_str(",\"forms\":");
out.push_str(&run.forms.to_string());
out.push_str(",\"results\":");
push_json_array(&mut out, &run.results);
out.push_str(",\"checks\":[");
for (index, check) in run.checks.iter().enumerate() {
comma(&mut out, index);
out.push_str("{\"form\":");
out.push_str(&check.form.to_string());
out.push_str(",\"expected\":");
push_json_str(&mut out, &check.expected);
out.push_str(",\"actual\":");
push_json_str(&mut out, &check.actual);
out.push_str(",\"pass\":");
out.push_str(if check.pass { "true" } else { "false" });
out.push('}');
}
out.push_str("]}");
out
}
fn push_recipe_summary(out: &mut String, card: &RecipeCard) {
out.push_str("{\"id\":");
push_json_str(out, &card.id);
out.push_str(",\"title\":");
push_json_str(out, &card.title);
out.push_str(",\"book\":");
push_json_str(out, &card.book);
out.push_str(",\"chapter\":");
push_json_str(out, &card.chapter);
out.push('}');
}
fn push_json_array(out: &mut String, items: &[String]) {
out.push('[');
for (index, item) in items.iter().enumerate() {
comma(out, index);
push_json_str(out, item);
}
out.push(']');
}
fn push_json_str(out: &mut String, text: &str) {
out.push('"');
for ch in text.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
ch if ch < ' ' => out.push_str(&format!("\\u{:04x}", ch as u32)),
ch => out.push(ch),
}
}
out.push('"');
}
fn comma(out: &mut String, index: usize) {
if index > 0 {
out.push(',');
}
}