1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use chrono::DateTime;
5use chrono::{Utc};
6use regex::Regex;
7use thiserror::Error;
8use validator::Validate;
9
10#[derive(Error, Debug)]
11pub enum UnknownEnumValueError {
12 TrainingType(String),
13 Sport(String),
14 BuildType(String),
15 Intensity(String),
16 TriggerMethod(String),
17 SensorState(String),
18 CadenceSensorType(String),
19}
20
21impl Display for UnknownEnumValueError {
22 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23 match self {
24 UnknownEnumValueError::TrainingType(t) => write!(f, "unknown '{}' training type", t),
25 UnknownEnumValueError::Sport(t) => write!(f, "unknown '{}' sport", t),
26 UnknownEnumValueError::BuildType(t) => write!(f, "unknown '{}' build type", t),
27 UnknownEnumValueError::Intensity(t) => write!(f, "unknown '{}' intensity", t),
28 UnknownEnumValueError::TriggerMethod(t) => write!(f, "unknown '{}' trigger method", t),
29 UnknownEnumValueError::SensorState(t) => write!(f, "unknown '{}' sensor state", t),
30 UnknownEnumValueError::CadenceSensorType(t) => {
31 write!(f, "unknown '{}' cadence sensor type", t)
32 }
33 }
34 }
35}
36
37#[derive(Debug, PartialEq)]
38pub enum SourceType {
39 Application(Application),
40 Device(Device),
41}
42
43#[derive(Debug, PartialEq)]
44pub enum BuildType {
45 Internal,
46 Alpha,
47 Beta,
48 Release,
49}
50
51impl FromStr for BuildType {
52 type Err = UnknownEnumValueError;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 match s {
56 "Internal" => Ok(BuildType::Internal),
57 "Alpha" => Ok(BuildType::Alpha),
58 "Beta" => Ok(BuildType::Beta),
59 "Release" => Ok(BuildType::Release),
60 _ => {
61 return Err(UnknownEnumValueError::BuildType(s.to_string()));
62 }
63 }
64 }
65}
66
67#[derive(Debug, PartialEq)]
68pub enum CoursePointType {
69 Generic,
70 Summit,
71 Valley,
72 Water,
73 Food,
74 Danger,
75 Left,
76 Right,
77 Straight,
78 FirstAid,
79 Category4,
80 Category3,
81 Category2,
82 Category1,
83 HorsCategory,
84 Sprint,
85}
86
87#[derive(Debug, PartialEq)]
88pub enum StepType {
89 Step(Step),
90 Repeat(Repeat),
91}
92
93#[derive(Debug, PartialEq)]
94pub enum Target {
95 Speed(Zone),
96 HeartRate(Zone),
97 Cadence(Cadence),
98 None,
99}
100
101#[derive(Debug, PartialEq)]
102pub enum Zone {
103 PredefinedSpeedZone(u8),
104 CustomSpeedZone(CustomSpeedZone),
105 PredefinedHeartRateZone(u8),
106 CustomHeartRateZone(CustomHeartRateZone),
107}
108
109#[derive(Debug, PartialEq)]
110pub enum SpeedType {
111 Pace,
112 Speed,
113}
114
115#[derive(Debug, PartialEq)]
116pub enum Duration {
117 Time(u16),
118 Distance(u16),
119 HeartRateAbove(u8),
120 HeartRateBelow(u8),
121 CaloriesBurned(u16),
122}
123
124#[derive(Debug, PartialEq)]
125pub enum TrainingType {
126 Workout,
127 Course,
128}
129
130impl Default for TrainingType {
131 fn default() -> Self {
132 TrainingType::Workout
133 }
134}
135
136impl FromStr for TrainingType {
137 type Err = UnknownEnumValueError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 match s {
141 "Workout" => Ok(TrainingType::Workout),
142 "Course" => Ok(TrainingType::Course),
143 _ => {
144 return Err(UnknownEnumValueError::TrainingType(s.to_string()));
145 }
146 }
147 }
148}
149
150#[derive(Debug, PartialEq)]
151pub enum SensorState {
152 Present,
153 Absent,
154}
155
156impl Default for SensorState {
157 fn default() -> Self {
158 Self::Present
159 }
160}
161
162impl FromStr for SensorState {
163 type Err = UnknownEnumValueError;
164
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 match s {
167 "Present" => Ok(Self::Present),
168 "Absent" => Ok(Self::Absent),
169 _ => {
170 return Err(UnknownEnumValueError::SensorState(s.to_string()));
171 }
172 }
173 }
174}
175
176#[derive(Debug, PartialEq)]
177pub enum Intensity {
178 Active,
179 Resting,
180}
181
182impl FromStr for Intensity {
183 type Err = UnknownEnumValueError;
184
185 fn from_str(s: &str) -> Result<Self, Self::Err> {
186 match s {
187 "Active" => Ok(Self::Active),
188 "Resting" => Ok(Self::Resting),
189 _ => {
190 return Err(UnknownEnumValueError::Intensity(s.to_string()));
191 }
192 }
193 }
194}
195
196#[derive(Debug, PartialEq)]
197pub enum TriggerMethod {
198 Manual,
199 Distance,
200 Location,
201 Time,
202 HeartRate,
203}
204
205impl FromStr for TriggerMethod {
206 type Err = UnknownEnumValueError;
207
208 fn from_str(s: &str) -> Result<Self, Self::Err> {
209 match s {
210 "Manual" => Ok(Self::Manual),
211 "Distance" => Ok(Self::Distance),
212 "Location" => Ok(Self::Location),
213 "Time" => Ok(Self::Time),
214 "HeartRate" => Ok(Self::HeartRate),
215 _ => {
216 return Err(UnknownEnumValueError::TriggerMethod(s.to_string()));
217 }
218 }
219 }
220}
221
222#[derive(Debug, PartialEq)]
223pub enum Sport {
224 Running,
225 Biking,
226 Other,
227}
228
229impl FromStr for Sport {
230 type Err = UnknownEnumValueError;
231
232 fn from_str(s: &str) -> Result<Self, Self::Err> {
233 match s {
234 "Running" => Ok(Sport::Running),
235 "Biking" => Ok(Sport::Biking),
236 "Other" => Ok(Sport::Other),
237 _ => {
238 return Err(UnknownEnumValueError::Sport(s.to_string()));
239 }
240 }
241 }
242}
243
244lazy_static! {
245 static ref PART_NUMBER_REGEX: Regex =
246 Regex::new(r"[\p{Lu}\d]{3}-[\p{Lu}\d]{5}-[\p{Lu}\d]{2}").unwrap();
247}
248
249#[derive(Default, Debug, PartialEq, Validate)]
251pub struct Application {
252 pub name: String,
253 pub build: Build,
254 #[validate(length(equal = 2))]
258 pub lang_id: String,
259 #[validate(regex = "PART_NUMBER_REGEX")]
261 pub part_number: String,
262}
263
264#[derive(Default, Debug, PartialEq)]
266pub struct Build {
267 pub version: Version,
268 pub build_type: Option<BuildType>,
269 pub time: Option<String>,
274 pub builder: Option<String>,
276}
277
278#[derive(Default, Debug, PartialEq)]
279pub struct Version {
280 pub version_major: u16,
281 pub version_minor: u16,
282 pub build_major: Option<u16>,
283 pub build_minor: Option<u16>,
284}
285
286#[derive(Default, Debug, PartialEq)]
290pub struct Device {
291 pub name: String,
292 pub unit_id: u32,
293 pub product_id: u16,
294 pub version: Version,
295}
296
297#[derive(Debug, PartialEq)]
298pub struct TrainingCenterDatabase {
299 pub folders: Option<Folders>,
300 pub activity_list: Option<ActivityList>,
301 pub workout_list: Option<WorkoutList>,
302 pub course_list: Option<CourseList>,
303 pub author: Option<SourceType>,
304}
305
306#[derive(Debug, PartialEq)]
307pub struct CourseList {
308 pub cources: Option<Vec<Course>>,
309}
310
311#[derive(Debug, PartialEq)]
312pub struct Course {
313 pub name: Option<String>,
314 pub laps: Option<Vec<CourseLap>>,
315 pub track_points: Option<Vec<TrackPoint>>,
316 pub notes: Option<String>,
317 pub course_point: Option<CoursePoint>,
318 pub creator: Option<SourceType>,
319}
320
321#[derive(Debug, PartialEq)]
322pub struct CoursePoint {
323 pub name: Option<String>,
324 pub time: Option<DateTime<Utc>>,
325 pub position: Option<Position>,
326 pub altitude_meters: Option<f64>,
327 pub point_type: Option<CoursePointType>,
328 pub notes: Option<String>,
329}
330
331#[derive(Debug, PartialEq, Validate)]
332pub struct CourseLap {
333 pub total_time_seconds: Option<f64>,
334 pub distance_meters: Option<f64>,
335 pub begin_position: Option<Position>,
336 pub begin_altitude_meters: Option<f64>,
337 pub end_position: Option<Position>,
338 pub end_altitude_meters: Option<f64>,
339 pub average_heart_rate_bpm: Option<u8>,
340 pub maximum_heart_rate_bpm: Option<u8>,
341 pub intensity: Option<Intensity>,
342 #[validate(range(max = 254))]
343 pub cadence: Option<u8>,
344}
345
346#[derive(Debug, PartialEq)]
347pub struct WorkoutList {
348 pub workouts: Option<Vec<Workout>>,
349}
350
351#[derive(Debug, PartialEq)]
352pub struct Workout {
353 pub name: Option<String>,
354 pub steps: Option<Vec<StepType>>,
355 pub scheduled_on: Option<DateTime<Utc>>,
356 pub notes: Option<String>,
357 pub creator: Option<SourceType>,
358 pub sport: Option<Sport>,
359}
360
361#[derive(Debug, PartialEq)]
362pub struct Repeat {
363 pub step_id: Option<u8>,
364 pub repetitions: Option<u8>,
365 pub children: Option<Vec<StepType>>,
366}
367
368#[derive(Debug, PartialEq)]
369pub struct Step {
370 pub step_id: Option<u8>,
371 pub name: Option<String>,
372 pub duration: Option<Duration>,
373 pub intensity: Option<Intensity>,
374 pub target: Option<Target>,
375}
376
377#[derive(Debug, PartialEq)]
378pub struct Cadence {
379 pub low: Option<f64>,
380 pub high: Option<f64>,
381}
382
383#[derive(Debug, PartialEq)]
384pub struct CustomHeartRateZone {
385 pub low: Option<u8>,
386 pub high: Option<u8>,
387}
388
389#[derive(Debug, PartialEq)]
390pub struct CustomSpeedZone {
391 pub view_as: Option<SpeedType>,
392 pub low_in_meters_per_second: Option<f64>,
393 pub high_in_meters_per_second: Option<f64>,
394}
395
396#[derive(Debug, PartialEq, Default)]
397pub struct ActivityList {
398 pub activities: Vec<Activity>,
399 pub multi_sport_sessions: Vec<MultiSportSession>,
400}
401
402#[derive(Debug, PartialEq)]
403pub struct MultiSportSession {
404 pub id: Option<DateTime<Utc>>,
405 pub sports: Option<Vec<MultiActivity>>,
406 pub notes: Option<String>,
407}
408
409#[derive(Debug, PartialEq)]
410pub struct MultiActivity {
411 pub transition: Option<ActivityLap>,
412 pub activity: Option<Activity>,
413}
414
415#[derive(Debug, PartialEq)]
416pub struct Folders {
417 pub history: Option<History>,
418 pub workouts: Option<Workouts>,
419 pub courses: Option<Courses>,
420}
421
422#[derive(Debug, PartialEq)]
423pub struct Courses {
424 pub course_folder: Option<CourseFolder>,
425}
426
427#[derive(Debug, PartialEq)]
428pub struct CourseFolder {
429 pub folders: Option<Vec<CourseFolder>>,
430 pub course_name_refs: Option<Vec<String>>,
431 pub notes: Option<String>,
432 pub name: Option<String>,
433}
434
435#[derive(Debug, PartialEq)]
436pub struct Workouts {
437 pub running: Option<WorkoutFolder>,
438 pub biking: Option<WorkoutFolder>,
439 pub other: Option<WorkoutFolder>,
440}
441
442#[derive(Debug, PartialEq)]
443pub struct WorkoutFolder {
444 pub folders: Option<Vec<WorkoutFolder>>,
445 pub workout_name_refs: Option<Vec<String>>,
446 pub name: Option<String>,
447}
448
449#[derive(Debug, PartialEq)]
450pub struct History {
451 pub running: Option<HistoryFolder>,
452 pub biking: Option<HistoryFolder>,
453 pub other: Option<HistoryFolder>,
454 pub multi_sport: Option<MultiSportFolder>,
455}
456
457#[derive(Debug, PartialEq)]
458pub struct MultiSportFolder {
459 pub folders: Option<Vec<MultiSportFolder>>,
460 pub multisport_activity_refs: Option<Vec<DateTime<Utc>>>,
461 pub weeks: Option<Vec<Week>>,
462 pub notes: Option<String>,
463 pub name: Option<String>,
464}
465
466#[derive(Debug, PartialEq)]
467pub struct HistoryFolder {
468 pub folders: Option<Vec<HistoryFolder>>,
469 pub activity_refs: Option<Vec<DateTime<Utc>>>,
470 pub weeks: Option<Vec<Week>>,
471 pub notes: Option<String>,
472 pub name: Option<String>,
473}
474
475#[derive(Debug, PartialEq)]
477pub struct Week {
478 pub notes: Option<String>,
479 pub start_day: Option<DateTime<Utc>>,
480}
481
482#[derive(Debug, PartialEq)]
483pub struct Activity {
484 pub id: DateTime<Utc>,
485 pub laps: Vec<ActivityLap>,
486 pub notes: Option<String>,
487 pub training: Option<Training>,
488 pub creator: Option<SourceType>,
489 pub sport: Sport,
490}
491
492impl Default for Activity {
493 fn default() -> Self {
494 Self {
495 id: Utc::now(),
496 laps: Vec::default(),
497 notes: None,
498 training: None,
499 creator: None,
500 sport: Sport::Running,
501 }
502 }
503}
504
505#[derive(Debug, PartialEq, Default)]
506pub struct Training {
507 pub quick_workout_results: Option<QuickWorkout>,
508 pub plan: Option<Plan>,
509 pub virtual_partner: bool,
510}
511
512#[derive(Debug, PartialEq, Default, Validate)]
513pub struct Plan {
514 #[validate(length(min = 1, max = 15))]
516 pub name: Option<String>,
517 pub training_type: TrainingType,
518 pub interval_workout: bool,
519}
520
521#[derive(Debug, PartialEq, Default)]
522pub struct QuickWorkout {
523 pub total_time_seconds: f64,
524 pub distance_meters: f64,
525}
526
527#[derive(Debug, PartialEq, Validate)]
528pub struct ActivityLap {
529 pub total_time_seconds: f64,
530 pub distance_meters: f64,
531 pub maximum_speed: Option<f64>,
532 pub calories: u16,
533 #[validate(range(min = 1))]
534 pub average_heart_rate_bpm: Option<u8>,
535 #[validate(range(min = 1))]
536 pub maximum_heart_rate_bpm: Option<u8>,
537 pub intensity: Intensity,
538 #[validate(range(max = 254))]
539 pub cadence: Option<u8>,
540 pub trigger_method: TriggerMethod,
541 pub track_points: Vec<TrackPoint>,
542 pub notes: Option<String>,
543 pub start_time: DateTime<Utc>,
544 pub extension: Option<ActivityLapExtension>,
545}
546
547impl Default for ActivityLap {
548 fn default() -> Self {
549 Self {
550 total_time_seconds: 0.0,
551 distance_meters: 0.0,
552 maximum_speed: None,
553 calories: 0,
554 average_heart_rate_bpm: None,
555 maximum_heart_rate_bpm: None,
556 intensity: Intensity::Active,
557 cadence: None,
558 trigger_method: TriggerMethod::Manual,
559 track_points: Vec::default(),
560 notes: None,
561 start_time: Utc::now(),
562 extension: None,
563 }
564 }
565}
566
567#[derive(Debug, PartialEq, Validate)]
568pub struct TrackPoint {
569 pub time: DateTime<Utc>,
570 pub position: Option<Position>,
571 pub altitude_meters: Option<f64>,
572 pub distance_meters: Option<f64>,
573 pub heart_rate_bpm: Option<u8>,
574 #[validate(range(max = 254))]
575 pub cadence: Option<u8>,
576 pub sensor_state: Option<SensorState>,
577 pub extension: Option<ActivityTrackPointExtension>,
578}
579
580impl Default for TrackPoint {
581 fn default() -> Self {
582 Self {
583 time: Utc::now(),
584 position: None,
585 altitude_meters: None,
586 distance_meters: None,
587 heart_rate_bpm: None,
588 cadence: None,
589 sensor_state: None,
590 extension: None,
591 }
592 }
593}
594
595#[derive(Debug, PartialEq, Default, Validate)]
596pub struct Position {
597 #[validate(range(min = - 90.0, max = 90.0))]
598 pub latitude_degrees: f64,
599 #[validate(range(min = - 180.0, max = 180.0))]
600 pub longitude_degrees: f64,
601}
602
603#[derive(Debug, PartialEq)]
606pub enum CadenceSensorType {
607 Footpod,
608 Bike,
609}
610
611impl FromStr for CadenceSensorType {
612 type Err = UnknownEnumValueError;
613
614 fn from_str(s: &str) -> Result<Self, Self::Err> {
615 match s {
616 "Footpod" => Ok(Self::Footpod),
617 "Bike" => Ok(Self::Bike),
618 _ => {
619 return Err(UnknownEnumValueError::CadenceSensorType(s.to_string()));
620 }
621 }
622 }
623}
624
625#[derive(Debug, PartialEq, Default, Validate)]
626pub struct ActivityTrackPointExtension {
627 pub speed: Option<f64>,
628 #[validate(range(max = 254))]
629 pub run_cadence: Option<u8>,
630 pub watts: Option<u16>,
631 pub cadence_sensor: Option<CadenceSensorType>,
632}
633
634#[derive(Debug, PartialEq, Default, Validate)]
635pub struct ActivityLapExtension {
636 pub avg_speed: Option<f64>,
637 #[validate(range(max = 254))]
638 pub max_bike_cadence: Option<u8>,
639 #[validate(range(max = 254))]
640 pub avg_run_cadence: Option<u8>,
641 #[validate(range(max = 254))]
642 pub max_run_cadence: Option<u8>,
643 pub steps: Option<u16>,
644 pub avg_watts: Option<u16>,
645 pub max_watts: Option<u16>,
646}