use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::cookbook::Recipe;
use crate::status::Status;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum RecipeUpdate {
Recipes {
data: Vec<Recipe>,
},
#[serde(rename = "status_changed")]
StatusChanged {
recipe: String,
status: Status,
},
}
impl RecipeUpdate {
pub fn to_jsonl(&self) -> String {
serde_json::to_string(self).expect("RecipeUpdate is always serializable")
}
}
pub fn serve<F>(interval: Duration, mut build: F) -> !
where
F: FnMut() -> Vec<Recipe>,
{
serve_updates(interval, move || {
vec![RecipeUpdate::Recipes { data: build() }]
})
}
pub fn serve_updates<F>(interval: Duration, mut build: F) -> !
where
F: FnMut() -> Vec<RecipeUpdate>,
{
let mut last: Option<String> = None;
loop {
let lines: Vec<String> = build().iter().map(RecipeUpdate::to_jsonl).collect();
let batch = lines.join("\n");
if last.as_deref() != Some(batch.as_str()) {
for line in &lines {
println!("{}", line);
}
last = Some(batch);
}
std::thread::sleep(interval);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recipes_roundtrip_through_json() {
let update = RecipeUpdate::Recipes {
data: vec![Recipe::new("foo"), Recipe::with_description("bar", "desc")],
};
let line = update.to_jsonl();
let parsed: RecipeUpdate = serde_json::from_str(&line).unwrap();
assert_eq!(update, parsed);
}
#[test]
fn recipes_serializes_with_type_tag() {
let update = RecipeUpdate::Recipes { data: vec![] };
let line = update.to_jsonl();
assert!(line.contains(r#""type":"recipes""#));
}
}