use crate::embed::recipes_from_embedded;
use crate::model::RecipeCard;
#[derive(Clone, Debug, Default)]
pub struct RecipeStore {
cards: Vec<RecipeCard>,
}
impl RecipeStore {
pub fn new() -> Self {
Self::default()
}
pub fn register_book(&mut self, dir: &[(&str, &[u8])]) -> Result<(), String> {
let cards = recipes_from_embedded(dir)?;
for card in &cards {
if self.cards.iter().any(|existing| existing.id == card.id) {
return Err(format!("duplicate recipe id `{}`", card.id));
}
}
self.cards.extend(cards);
Ok(())
}
pub fn insert_card(&mut self, card: RecipeCard) -> Result<(), String> {
if self.cards.iter().any(|existing| existing.id == card.id) {
return Err(format!("duplicate recipe id `{}`", card.id));
}
self.cards.push(card);
Ok(())
}
pub fn upsert_card(&mut self, card: RecipeCard) -> bool {
match self
.cards
.iter_mut()
.find(|existing| existing.id == card.id)
{
Some(existing) => {
*existing = card;
true
}
None => {
self.cards.push(card);
false
}
}
}
pub fn remove(&mut self, id: &str) -> bool {
let before = self.cards.len();
self.cards.retain(|c| c.id != id);
self.cards.len() != before
}
pub fn card_mut(&mut self, id: &str) -> Option<&mut RecipeCard> {
self.cards.iter_mut().find(|c| c.id == id)
}
pub fn cards(&self) -> &[RecipeCard] {
&self.cards
}
pub fn card(&self, id: &str) -> Option<&RecipeCard> {
self.cards.iter().find(|c| c.id == id)
}
pub fn len(&self) -> usize {
self.cards.len()
}
pub fn is_empty(&self) -> bool {
self.cards.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn book(id: &'static str) -> Vec<(&'static str, &'static [u8])> {
match id {
"alpha" => vec![
("book.toml", b"book = \"alpha\"\ntitle = \"Alpha\"\n" as &[u8]),
(
"c/r/recipe.toml",
b"id = \"r\"\ntitle = \"R\"\ncodec = \"lisp\"\nsetup = \"s\"\npurpose = \"p\"\n",
),
("c/r/s", b"(quote a)"),
("c/r/p", b"alpha recipe"),
],
_ => vec![
("book.toml", b"book = \"beta\"\ntitle = \"Beta\"\n" as &[u8]),
(
"c/r/recipe.toml",
b"id = \"r\"\ntitle = \"R\"\ncodec = \"lisp\"\nsetup = \"s\"\npurpose = \"p\"\n",
),
("c/r/s", b"(quote b)"),
("c/r/p", b"beta recipe"),
],
}
}
#[test]
fn registers_multiple_books() {
let mut store = RecipeStore::new();
store.register_book(&book("alpha")).unwrap();
store.register_book(&book("beta")).unwrap();
assert_eq!(store.len(), 2);
assert!(store.card("alpha/c/r").is_some());
assert!(store.card("beta/c/r").is_some());
}
#[test]
fn rejects_duplicate_book() {
let mut store = RecipeStore::new();
store.register_book(&book("alpha")).unwrap();
let err = store.register_book(&book("alpha")).unwrap_err();
assert!(err.contains("duplicate recipe id `alpha/c/r`"), "{err}");
}
}