// SPDX-FileCopyrightText: 2022 Matteo Settenvini // SPDX-License-Identifier: AGPL-3.0-or-later use { chrono::Duration, serde::{Deserialize, Deserializer}, }; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Metadata { #[serde(rename = "recipe_id")] pub id: u32, pub name: String, pub keywords: String, pub date_created: DateTime, pub date_modified: DateTime, } /// A recipe according to [schema.org](http://schema.org/Recipe) #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Recipe { pub id: isize, pub name: String, pub description: String, pub url: String, pub keywords: String, #[serde(rename = "dateCreated")] pub created: DateTime, #[serde(rename = "dateModified")] pub modified: Option, pub image_url: String, #[serde(deserialize_with = "deserialize_duration")] pub prep_time: Duration, #[serde(default)] #[serde(deserialize_with = "deserialize_maybe_duration")] pub cook_time: Option, #[serde(default)] #[serde(deserialize_with = "deserialize_maybe_duration")] pub total_time: Option, pub image: Option, pub recipe_yield: isize, #[serde(rename = "recipeCategory")] pub category: Option, pub tools: Option>, #[serde(rename = "recipeIngredient")] pub ingredients: Vec, #[serde(rename = "recipeInstructions")] pub instructions: Vec, //pub nutrition: Nutrition, } #[derive(Deserialize, Debug, Ord, Eq, PartialEq, PartialOrd)] #[serde(rename_all = "camelCase")] pub struct Ingredient(pub String); #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Tool(pub String); #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Instruction(pub String); #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Nutrition { pub calories: Option, pub carbohydrates_content: Option, pub cholesterol_content: Option, pub fat_content: Option, pub fiber_content: Option, pub protein_content: Option, pub saturated_fat_content: Option, pub serving_size: Option, pub sodium_content: Option, pub sugar_content: Option, pub trans_fat_content: Option, pub unsaturated_fat_content: Option, } type DateTime = chrono::DateTime; fn deserialize_maybe_duration<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { Ok(Some(deserialize_duration(deserializer)?)) } fn deserialize_duration<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(DurationVisitor) } struct DurationVisitor; impl<'de> serde::de::Visitor<'de> for DurationVisitor { type Value = Duration; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a duration in ISO 8601 format") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { speedate::Duration::parse_str(value) .map(|dt| Duration::seconds(dt.signed_total_seconds())) .map_err(|e| E::custom(e.to_string())) } } impl Recipe { pub fn total_time(&self) -> Duration { self.total_time .unwrap_or_else(|| self.prep_time + self.cook_time.unwrap_or(Duration::zero())) } }