// rocie - An enterprise grocery management system // // Copyright (C) 2026 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Rocie. // // You should have received a copy of the License along with this program. // If not, see . use crate::{ app::App, storage::sql::{ recipe::{CooklangRecipe, Recipe, RecipeId}, recipe_parent::RecipeParentId, }, }; use sqlx::query; pub(crate) mod parse { use crate::storage::sql::recipe::conversion; #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("Failed to convert from cooklang recipe to our own struct")] Conversion(#[from] conversion::Error), } } async fn recipe_from_content(app: &App, content: &str) -> Result { // NOTE: We can ignore warnings here, as we should already have handled them at the recipe // insert point. <2026-01-31> let (output, _warnings) = cooklang::parse(content) .into_result() .expect("The values in the db should always be valid, as we checked before inserting them"); Ok(CooklangRecipe::from(app, output).await?) } impl Recipe { pub(crate) async fn from_id(app: &App, id: RecipeId) -> Result, from_id::Error> { let record = query!( " SELECT name, parent, content FROM recipies WHERE id = ? ", id ) .fetch_optional(&app.db) .await?; if let Some(record) = record { Ok(Some(Self { id, content: recipe_from_content(app, &record.content).await?, name: record.name, parent: record.parent.map(|id| RecipeParentId::from_db(&id)), })) } else { Ok(None) } } pub(crate) async fn from_name(app: &App, name: String) -> Result, from_id::Error> { let record = query!( " SELECT id, parent, content FROM recipies WHERE name = ? ", name ) .fetch_optional(&app.db) .await?; if let Some(record) = record { Ok(Some(Self { id: RecipeId::from_db(&record.id), content: recipe_from_content(app, &record.content).await?, name, parent: record.parent.map(|id| RecipeParentId::from_db(&id)), })) } else { Ok(None) } } pub(crate) async fn get_all(app: &App) -> Result, get_all::Error> { let records = query!( " SELECT id, name, parent, content FROM recipies ", ) .fetch_all(&app.db) .await?; let mut output = vec![]; for record in records { output.push(Self { id: RecipeId::from_db(&record.id), content: recipe_from_content(app, &record.content).await?, name: record.name, parent: record.parent.map(|id| RecipeParentId::from_db(&id)), }); } Ok(output) } } pub(crate) mod from_id { use actix_web::ResponseError; use crate::storage::sql::get::recipe::parse; #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("Failed to execute the sql query: `{0}`")] SqlError(#[from] sqlx::Error), #[error("Failed to parse the recipe content as cooklang recipe: `{0}`")] RecipeParse(#[from] parse::Error), } impl ResponseError for Error {} } pub(crate) mod get_all { use actix_web::ResponseError; use crate::storage::sql::get::recipe::parse; #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("Failed to execute the sql query")] SqlError(#[from] sqlx::Error), #[error("Failed to parse the recipe content as cooklang recipe")] RecipeParse(#[from] parse::Error), } impl ResponseError for Error {} }