use super::schema::recipes; use super::schema::ingredients; use super::diesel::prelude::*; pub mod fields { use diesel::{ backend::Backend, sql_types::*, deserialize::{self, FromSql}, serialize::{self, Output, ToSql}, }; use std::io::Write; /// All recipes have a single associated category /// representing the main use of the resulting preparation. /// /// It is stored as Integer #[derive(Debug, Copy, Clone, Eq, PartialEq, FromSqlRow, AsExpression)] #[sql_type = "SmallInt"] pub enum RecipeCategory { Breakfast = 0, Starter = 1, MainCourse = 2, Dessert = 3 } impl RecipeCategory { pub fn id(&self) -> i16 { *self as i16 } pub fn name(&self) -> &str { match *self { RecipeCategory::Breakfast => "Petit-déjeuner", RecipeCategory::Starter => "Entrée", RecipeCategory::MainCourse => "Plat principal", RecipeCategory::Dessert => "Dessert" } } pub fn all() -> [Self; 4] { [RecipeCategory::Breakfast, RecipeCategory::Starter, RecipeCategory::MainCourse, RecipeCategory::Dessert] } pub fn from_id(id: i16) -> Option { match id { 0 => Some(RecipeCategory::Breakfast), 1 => Some(RecipeCategory::Starter), 2 => Some(RecipeCategory::MainCourse), 3 => Some(RecipeCategory::Dessert), _ => None, } } } impl FromSql for RecipeCategory where i16: FromSql { fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result { let v = i16::from_sql(bytes)?; if let Some(result) = RecipeCategory::from_id(v){ Ok(result) } else { Err("Invalid RecipeCategory id".into()) } } } impl ToSql for RecipeCategory where i16: ToSql { fn to_sql(&self, out: &mut Output) -> serialize::Result { i16::to_sql(&(*self as i16), out) } } pub type IngredientId = i32; #[derive(Debug, Clone, FromSqlRow, AsExpression)] #[sql_type = "Text"] pub struct IngredientList(Vec); /// Just a basic method for now impl IngredientList { pub fn new() -> Self { IngredientList(Vec::new()) } pub fn as_string(&self) -> String { self.0.iter() .map(|i| format!("{}", i)) .collect::>() .join(" ") } } impl std::ops::Deref for IngredientList { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for IngredientList { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl FromSql for IngredientList where String: FromSql { fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result { let data = String::from_sql(bytes)?; Ok( IngredientList( data.split(" ") .map(|i| i.parse::().unwrap()) .collect() )) } } impl ToSql for IngredientList where String: ToSql { fn to_sql(&self, out: &mut Output) -> serialize::Result { String::to_sql(&self.as_string(), out) } } } /// Data for a recipe stored in DB #[derive(Debug, Clone, Queryable)] pub struct Recipe { pub id: i32, pub title: String, pub category: fields::RecipeCategory, pub ingredients: fields::IngredientList, pub preparation: String, } impl PartialEq for Recipe { fn eq(&self, other: &Recipe) -> bool { self.id == other.id } } #[derive(Insertable, Debug)] #[table_name="recipes"] pub struct NewRecipe<'a> { pub title: &'a str, pub category: fields::RecipeCategory, pub ingredients: fields::IngredientList, pub preparation: &'a str, } impl<'a> NewRecipe<'a> { pub fn insert(self, conn: &SqliteConnection) -> Result { diesel::insert_into(recipes::table) .values(&self) .execute(conn) .expect("Error inserting recipe"); Ok(self) } } #[derive(Queryable)] pub struct Ingredient { pub id: i32, pub alias: String, } #[derive(Insertable)] #[table_name="ingredients"] pub struct NewIngredient<'a> { pub alias: &'a str, }