fitbit_rs/
activity_summary.rs

1use serde::Deserialize;
2
3#[derive(Debug, Deserialize)]
4pub struct ActivitySummaryResponse {
5    pub activities: Vec<Activity>,
6    pub summary: Summary,
7    pub goals: Goals,
8}
9
10impl ActivitySummaryResponse {
11    pub fn get_steps(&self) -> u32 {
12        self.summary.steps
13    }
14}
15
16#[derive(Debug, Deserialize)]
17pub struct Activity {
18    // TODO
19}
20
21#[derive(Debug, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct Summary {
24    pub calories_out: i32,
25    pub activity_calories: i32,
26    #[serde(rename = "caloriesBMR")]
27    pub calories_bmr: i32,
28    pub active_score: i32,
29    pub steps: u32,
30    pub floors: i32,
31    pub elevation: f64,
32    pub sedentary_minutes: i32,
33    pub lightly_active_minutes: i32,
34    pub fairly_active_minutes: i32,
35    pub very_active_minutes: i32,
36    pub distances: Vec<Distance>,
37    pub marginal_calories: i32,
38    pub resting_heart_rate: i32,
39    pub heart_rate_zones: Vec<HeartRateZone>,
40}
41
42#[derive(Debug, Deserialize, PartialEq)]
43#[serde(rename_all = "camelCase")]
44pub enum ActivityType {
45    Total,
46    Tracker,
47    LoggedActivities,
48    VeryActive,
49    ModeratelyActive,
50    LightlyActive,
51    SedentaryActive,
52}
53
54#[derive(Debug, Deserialize)]
55pub struct Distance {
56    pub activity: ActivityType,
57    pub distance: f64,
58}
59
60#[derive(Debug, Deserialize, PartialEq)]
61pub enum HeartRateZoneName {
62    #[serde(rename = "Out of Range")]
63    OutOfRange,
64    #[serde(rename = "Fat Burn")]
65    FatBurn,
66    Cardio,
67    Peak,
68}
69
70#[derive(Debug, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct HeartRateZone {
73    pub minutes: i32,
74    pub calories_out: f64,
75    pub name: HeartRateZoneName,
76    pub min: i32,
77    pub max: i32,
78}
79
80#[derive(Debug, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct Goals {
83    pub calories_out: i32,
84    pub steps: u32,
85    pub distance: f64,
86    pub floors: i32,
87    pub active_minutes: i32,
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_parse_fitbit_response() {
96        let json_str = r#"{
97            "activities": [],
98            "summary": {
99                "caloriesOut": 1746,
100                "activityCalories": 62,
101                "caloriesBMR": 668,
102                "activeScore": -1,
103                "steps": 27,
104                "floors": 0,
105                "elevation": 0.0,
106                "sedentaryMinutes": 552,
107                "lightlyActiveMinutes": 14,
108                "fairlyActiveMinutes": 0,
109                "veryActiveMinutes": 0,
110                "distances": [
111                    {"activity": "total", "distance": 0.0197},
112                    {"activity": "tracker", "distance": 0.0197},
113                    {"activity": "sedentaryActive", "distance": 0.0067},
114                    {"activity": "lightlyActive", "distance": 0.013},
115                    {"activity": "moderatelyActive", "distance": 0.0},
116                    {"activity": "veryActive", "distance": 0.0},
117                    {"activity": "loggedActivities", "distance": 0.0}
118                ],
119                "marginalCalories": 40,
120                "restingHeartRate": 60,
121                "heartRateZones": [
122                    {"minutes": 412, "caloriesOut": 529.8314, "name": "Out of Range", "min": 30, "max": 114},
123                    {"minutes": 1, "caloriesOut": 4.9539, "name": "Fat Burn", "min": 115, "max": 141},
124                    {"minutes": 0, "caloriesOut": 0.0, "name": "Cardio", "min": 142, "max": 176},
125                    {"minutes": 0, "caloriesOut": 0.0, "name": "Peak", "min": 177, "max": 220}
126                ]
127            },
128            "goals": {
129                "caloriesOut": 2545,
130                "steps": 8000,
131                "distance": 8.05,
132                "floors": 10,
133                "activeMinutes": 30
134            }
135        }"#;
136
137        let response: ActivitySummaryResponse =
138            serde_json::from_str(json_str).expect("Failed to parse JSON");
139
140        // Test summary fields
141        assert_eq!(response.summary.calories_out, 1746);
142        assert_eq!(response.summary.activity_calories, 62);
143        assert_eq!(response.summary.calories_bmr, 668);
144        assert_eq!(response.summary.active_score, -1);
145        assert_eq!(response.summary.steps, 27);
146        assert_eq!(response.summary.floors, 0);
147        assert_eq!(response.summary.elevation, 0.0);
148        assert_eq!(response.summary.sedentary_minutes, 552);
149        assert_eq!(response.summary.lightly_active_minutes, 14);
150        assert_eq!(response.summary.fairly_active_minutes, 0);
151        assert_eq!(response.summary.very_active_minutes, 0);
152        assert_eq!(response.summary.marginal_calories, 40);
153        assert_eq!(response.summary.resting_heart_rate, 60);
154
155        // Test distances
156        assert_eq!(response.summary.distances.len(), 7);
157        assert_eq!(response.summary.distances[0].activity, ActivityType::Total);
158        assert_eq!(response.summary.distances[0].distance, 0.0197);
159        assert_eq!(
160            response.summary.distances[2].activity,
161            ActivityType::SedentaryActive
162        );
163        assert_eq!(response.summary.distances[2].distance, 0.0067);
164
165        // Test heart rate zones
166        assert_eq!(response.summary.heart_rate_zones.len(), 4);
167        assert_eq!(
168            response.summary.heart_rate_zones[0].name,
169            HeartRateZoneName::OutOfRange
170        );
171        assert_eq!(response.summary.heart_rate_zones[0].minutes, 412);
172        assert_eq!(response.summary.heart_rate_zones[0].calories_out, 529.8314);
173        assert_eq!(response.summary.heart_rate_zones[0].min, 30);
174        assert_eq!(response.summary.heart_rate_zones[0].max, 114);
175
176        assert_eq!(
177            response.summary.heart_rate_zones[1].name,
178            HeartRateZoneName::FatBurn
179        );
180        assert_eq!(response.summary.heart_rate_zones[1].minutes, 1);
181
182        // Test goals
183        assert_eq!(response.goals.calories_out, 2545);
184        assert_eq!(response.goals.steps, 8000);
185        assert_eq!(response.goals.distance, 8.05);
186        assert_eq!(response.goals.floors, 10);
187        assert_eq!(response.goals.active_minutes, 30);
188    }
189}