about summary refs log tree commit diff stats
path: root/crates/rocie-server/src/storage/sql/recipe.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rocie-server/src/storage/sql/recipe.rs')
-rw-r--r--crates/rocie-server/src/storage/sql/recipe.rs100
1 files changed, 82 insertions, 18 deletions
diff --git a/crates/rocie-server/src/storage/sql/recipe.rs b/crates/rocie-server/src/storage/sql/recipe.rs
index 1fc3b56..7347b4b 100644
--- a/crates/rocie-server/src/storage/sql/recipe.rs
+++ b/crates/rocie-server/src/storage/sql/recipe.rs
@@ -1,5 +1,6 @@
 #![expect(clippy::unused_async)]
 
+use log::error;
 use serde::{Deserialize, Serialize};
 use utoipa::ToSchema;
 
@@ -10,7 +11,7 @@ use crate::{
         product::{Product, ProductId},
         product_amount::ProductAmount,
         recipe_parent::RecipeParentId,
-        unit::UnitAmount,
+        unit::{Unit, UnitAmount},
     },
 };
 
@@ -97,7 +98,6 @@ pub(crate) struct Section {
 
 /// Each type of content inside a section
 #[derive(Debug, ToSchema, Serialize, Deserialize, PartialEq, Clone)]
-#[serde(tag = "type", content = "value", rename_all = "camelCase")]
 pub(crate) enum Content {
     /// A step
     Step(Step),
@@ -124,7 +124,6 @@ pub(crate) struct Step {
 /// Except for [`Item::Text`], the value is the index where the item is located
 /// in it's corresponding [`Vec`] in the [`Recipe`].
 #[derive(Debug, ToSchema, Serialize, Deserialize, PartialEq, Clone)]
-#[serde(tag = "type", rename_all = "camelCase")]
 pub(crate) enum Item {
     /// Just plain text
     Text {
@@ -205,7 +204,8 @@ pub(crate) struct Timer {
     pub(crate) name: Option<String>,
 
     /// Time quantity
-    pub(crate) quantity: UnitAmount,
+    #[schema(nullable = false)]
+    pub(crate) quantity: Option<UnitAmount>,
 }
 
 #[derive(Debug, ToSchema, Serialize, Deserialize, PartialEq, Clone)]
@@ -238,12 +238,18 @@ pub(crate) struct Metadata {
 }
 
 pub(crate) mod conversion {
-    use crate::storage::sql::get::product::from_id;
+    use crate::storage::sql::get::{product::from_id, unit::from_name};
 
     #[derive(thiserror::Error, Debug)]
     pub(crate) enum Error {
         #[error("Failed to get a product by id: `{0}`")]
         ProductAccess(#[from] from_id::Error),
+
+        #[error("The quantity of a cookware has an unit: `{0}`")]
+        CookwareQtyHasUnit(cooklang::Quantity),
+
+        #[error("Failed to get a unit by name: `{0}`")]
+        UnitLookup(#[from] from_name::Error),
     }
 }
 
@@ -258,8 +264,8 @@ pub(crate) struct NameAndUrl {
 impl NameAndUrl {
     async fn from(value: cooklang::metadata::NameAndUrl) -> Result<Self, conversion::Error> {
         Ok(Self {
-            name: value.name().map(|v| v.to_owned()),
-            url: value.url().map(|v| v.to_owned()),
+            name: value.name().map(ToOwned::to_owned),
+            url: value.url().map(ToOwned::to_owned),
         })
     }
 }
@@ -274,7 +280,7 @@ impl CooklangRecipe {
             sections: for_in!(value.sections, |s| Section::from(s).await),
             ingredients: for_in!(value.ingredients, |i| Ingredient::from(app, i).await),
             cookware: for_in!(value.cookware, |c| Cookware::from(c).await),
-            timers: for_in!(value.timers, |t| Timer::from(t).await),
+            timers: for_in!(value.timers, |t| Timer::from(app, t).await),
         })
     }
 }
@@ -353,16 +359,22 @@ impl Ingredient {
     async fn from(app: &App, value: cooklang::Ingredient) -> Result<Self, conversion::Error> {
         if value.name.starts_with('/') {
             Ok(Self::RecipeReference { id: todo!() })
-        } else if let Some(product) = Product::from_name(&app, value.name.as_str()).await? {
+        } else if let Some(product) = Product::from_name(app, value.name.as_str()).await? {
             Ok(Self::RegisteredProduct {
                 id: product.id,
                 alias: value.alias,
-                quantity: None,
+                quantity: {
+                    let unit_amount = quantity_to_unit_amount(app, value.quantity).await?;
+                    unit_amount.map(|ua| ProductAmount {
+                        product_id: product.id,
+                        amount: ua,
+                    })
+                },
             })
         } else {
             Ok(Self::NotRegisteredProduct {
                 name: value.name,
-                quantity: None,
+                quantity: quantity_to_unit_amount(app, value.quantity).await?,
             })
         }
     }
@@ -372,20 +384,72 @@ impl Cookware {
         Ok(Self {
             name: value.name,
             alias: value.alias,
-            quantity: value.quantity.map(|q| match q.value() {
-                cooklang::Value::Number(number) => number.value() as usize,
-                cooklang::Value::Range { start, end } => todo!(),
-                cooklang::Value::Text(_) => todo!(),
-            }),
+            quantity: if let Some(qty) = value.quantity {
+                if qty.unit().is_some() {
+                    return Err(conversion::Error::CookwareQtyHasUnit(qty));
+                }
+
+                let var_name = match qty.value() {
+                    cooklang::Value::Number(number) => number.value() as usize,
+                    cooklang::Value::Range { .. } | cooklang::Value::Text(_) => todo!(),
+                };
+
+                Some(var_name)
+            } else {
+                None
+            },
             note: value.note,
         })
     }
 }
 impl Timer {
-    async fn from(value: cooklang::Timer) -> Result<Self, conversion::Error> {
+    async fn from(app: &App, value: cooklang::Timer) -> Result<Self, conversion::Error> {
         Ok(Self {
             name: value.name,
-            quantity: todo!(),
+            quantity: quantity_to_unit_amount(app, value.quantity).await?,
         })
     }
 }
+
+async fn quantity_to_unit_amount(
+    app: &App,
+    qty: Option<cooklang::Quantity>,
+) -> Result<Option<UnitAmount>, conversion::Error> {
+    if let Some(qty) = qty {
+        let amount: f64 = match qty.value() {
+            cooklang::Value::Number(number) => number.value(),
+            cooklang::Value::Range { start, end } => {
+                // Let's just assume that more is better than less?
+                // TODO: This should be mapped correctly <2026-03-15>
+
+                end.value()
+            }
+            cooklang::Value::Text(_) => {
+                // TODO: Is there maybe a better way to deal with, non-parsable quantities? <2026-03-15>
+                return Ok(None);
+            }
+        };
+
+        let unit = if let Some(unit_name) = qty.unit() {
+            let unit = Unit::from_name(app, unit_name).await?;
+
+            if unit.is_none() {
+                error!(
+                    "Failed to transfer the quantity for a recipe amount, as the unit is not yet known: {unit_name}"
+                );
+            }
+
+            unit
+        } else {
+            return Ok(None);
+        };
+
+        Ok(unit.map(|unit| UnitAmount {
+            // TODO: We need to convert the unit to the one that can fit the full f64 value as u32. <2026-03-15>
+            value: amount as u32,
+            unit: unit.id,
+        }))
+    } else {
+        Ok(None)
+    }
+}