use chrono::{DateTime, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use strum::{EnumIter, IntoStaticStr}; use uuid::Uuid; #[derive(sqlx::Type, Serialize, Deserialize, EnumIter, Debug, IntoStaticStr)] #[sqlx(type_name = "metric_type")] pub enum MetricType { BodyWeight, Hip, Waist, } #[derive(sqlx::Type, Serialize, Deserialize, EnumIter, Debug, IntoStaticStr)] #[sqlx(type_name = "unit")] pub enum Unit { Kilogram, Centimeter, } #[derive(Serialize)] pub struct MeasurementView { pub id: Uuid, pub measured_at: DateTime, pub metric_type: MetricType, pub value: f64, pub unit: Unit, } #[derive(Deserialize)] #[serde(try_from = "MeasurementDataUnchecked")] pub struct MeasurementData { pub measured_at: DateTime, pub metric_type: MetricType, pub value: f64, pub unit: Unit, } #[derive(Deserialize)] struct MeasurementDataUnchecked { pub measured_at: DateTime, pub metric_type: MetricType, pub value: f64, pub unit: Unit, } impl TryFrom for MeasurementData { type Error = String; fn try_from(value: MeasurementDataUnchecked) -> Result { if value.value < 0.0 || value.value.is_nan() { return Err("Invalid value".to_string()); } match (&value.metric_type, &value.unit) { (MetricType::BodyWeight, Unit::Kilogram) | (MetricType::Hip, Unit::Centimeter) | (MetricType::Waist, Unit::Centimeter) => Ok(Self { measured_at: value.measured_at, metric_type: value.metric_type, value: value.value, unit: value.unit, }), (metric_type, unit) => { let metric_type: &'static str = metric_type.into(); let unit: &'static str = unit.into(); Err(format!("Invalid pair {}, {}", metric_type, unit)) } } } } #[derive(Deserialize)] pub struct DateQuery { pub date: Option, pub from: Option, pub to: Option, } #[derive(Deserialize)] pub struct AllQuery { pub limit: Option, pub reverse: Option, } #[cfg(test)] mod tests { use super::*; use sqlx::PgPool; use strum::IntoEnumIterator; #[sqlx::test] async fn unit_enums_match(pool: PgPool) { let values: Vec = sqlx::query_scalar("SELECT enum_range(NULL::unit)") .fetch_one(&pool) .await .expect("Database has a variant that Rust doesn't"); assert_eq!( values.len(), Unit::iter().count(), "Rust has a variant that the database doesn't" ); } #[sqlx::test] async fn metric_type_enums_match(pool: PgPool) { let values: Vec = sqlx::query_scalar("SELECT enum_range(NULL::metric_type)") .fetch_one(&pool) .await .expect("Database has a variant that Rust doesn't"); assert_eq!( values.len(), MetricType::iter().count(), "Rust has a variant that the database doesn't" ); } }