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),
        }
    }
}