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