use chrono::{NaiveTime, Timelike}; use diesel::backend::Backend; use diesel::deserialize::FromSql; use diesel::serialize::{IsNull, Output, ToSql}; use diesel::sql_types::Binary; use diesel::sqlite::Sqlite; use diesel::{deserialize, serialize}; use serde::{Deserialize, Serialize}; use std::io::Write; use crate::db::models::Periods; #[derive(Debug, Serialize, Deserialize, AsExpression, FromSqlRow, PartialEq, Clone)] #[sql_type = "Binary"] pub struct Period { #[serde(with = "period_format")] pub start: NaiveTime, #[serde(with = "period_format")] pub end: NaiveTime, } mod period_format { use chrono::NaiveTime; use serde::{self, Deserialize, Deserializer, Serializer}; const FORMAT: &'static str = "%H:%M"; pub fn serialize<S>(time: &NaiveTime, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let s = format!("{}", time.format(FORMAT)); serializer.serialize_str(&s) } pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveTime, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; NaiveTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom) } } impl ToSql<Binary, Sqlite> for Periods { fn to_sql<W: Write>(&self, out: &mut Output<W, Sqlite>) -> serialize::Result { for period in self.0.iter() { out.write_all(&[ period.start.hour() as u8, period.start.minute() as u8, period.end.hour() as u8, period.end.minute() as u8, ])?; } Ok(IsNull::No) } } impl FromSql<Binary, Sqlite> for Periods { fn from_sql(bytes: Option<&<Sqlite as Backend>::RawValue>) -> deserialize::Result<Self> { let blob = bytes.unwrap().read_blob(); let mut vec = Vec::new(); for i in (3..blob.len()).step_by(4) { let start_val_h: u32 = blob[i - 3] as u32; let start_val_m: u32 = blob[i - 2] as u32; let end_val_h: u32 = blob[i - 1] as u32; let end_val_m: u32 = blob[i - 0] as u32; vec.push(Period { start: NaiveTime::from_hms(start_val_h, start_val_m, 0), end: NaiveTime::from_hms(end_val_h, end_val_m, 0), }); } Ok(Periods(vec)) } }