1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
use dimensioned::si::{Meter, Second}; use emseries::{DateTimeTz, Recordable}; use std::convert::TryFrom; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum ActivityType { Cycling, Rowing, Running, Swimming, Walking, } pub fn activity_types() -> Vec<ActivityType> { vec![ ActivityType::Cycling, ActivityType::Rowing, ActivityType::Running, ActivityType::Swimming, ActivityType::Walking, ] } impl TryFrom<&str> for ActivityType { type Error = &'static str; fn try_from(inp: &str) -> Result<ActivityType, Self::Error> { match inp { "Cycling" => Ok(ActivityType::Cycling), "Rowing" => Ok(ActivityType::Rowing), "Running" => Ok(ActivityType::Running), "Swimming" => Ok(ActivityType::Swimming), "Walking" => Ok(ActivityType::Walking), _ => Err("invalid activity string"), } } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TimeDistanceRecord { #[serde(rename = "date")] pub timestamp: DateTimeTz, pub activity: ActivityType, pub distance: Option<Meter<f64>>, pub duration: Option<Second<f64>>, pub comments: Option<String>, } impl TimeDistanceRecord { pub fn new( timestamp: DateTimeTz, activity: ActivityType, distance: Option<Meter<f64>>, duration: Option<Second<f64>>, comments: Option<String>, ) -> TimeDistanceRecord { TimeDistanceRecord { timestamp, activity, distance, duration, comments, } } } impl Recordable for TimeDistanceRecord { fn timestamp(&self) -> DateTimeTz { self.timestamp.clone() } fn tags(&self) -> Vec<String> { match self.activity { ActivityType::Cycling => vec![String::from("Cycling")], ActivityType::Rowing => vec![String::from("Rowing")], ActivityType::Running => vec![String::from("Running")], ActivityType::Swimming => vec![String::from("Swimming")], ActivityType::Walking => vec![String::from("Walking")], } } } #[cfg(test)] mod test { use super::{ActivityType, TimeDistanceRecord}; use dimensioned::si::{M, S}; #[test] pub fn deserialize_time_distance() { let cycling_track_str = "{\"distance\":12200,\"date\":\"2017-10-28T19:27:00Z\",\"activity\":\"Cycling\",\"comments\":null,\"duration\":3120}"; let cycle_track: TimeDistanceRecord = serde_json::from_str(cycling_track_str).unwrap(); assert_eq!(cycle_track.activity, ActivityType::Cycling); assert_eq!(cycle_track.distance, Some(12200. * M)); assert_eq!(cycle_track.duration, Some(3120. * S)); let running_track_str = "{\"distance\":3630,\"date\":\"2018-11-12T18:30:00Z\",\"activity\":\"Running\",\"comments\":null,\"duration\":1800}"; let running_track: Result<TimeDistanceRecord, serde_json::Error> = serde_json::from_str(running_track_str); match running_track { Ok(track) => { assert_eq!(track.activity, ActivityType::Running); assert_eq!(track.distance, Some(3630. * M)); assert_eq!(track.duration, Some(1800. * S)); } Err(err) => panic!(err), } } }