quick_tcx/
types.rs

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/// Identifies a PC software application.
250#[derive(Default, Debug, PartialEq, Validate)]
251pub struct Application {
252    pub name: String,
253    pub build: Build,
254    /// Specifies the two character ISO 693-1 language id that identifies the installed
255    /// language of this application. see http://www.loc.gov/standards/iso639-2/
256    /// for appropriate ISO identifiers
257    #[validate(length(equal = 2))]
258    pub lang_id: String,
259    /// The formatted XXX-XXXXX-XX Garmin part number of a PC application.
260    #[validate(regex = "PART_NUMBER_REGEX")]
261    pub part_number: String,
262}
263
264/// Information about the build.
265#[derive(Default, Debug, PartialEq)]
266pub struct Build {
267    pub version: Version,
268    pub build_type: Option<BuildType>,
269    /// A string containing the date and time when an application was built.
270    /// Note that this is not an xsd:dateTime type because this string is
271    /// generated by the compiler and cannot be readily converted to the
272    /// xsd:dateTime format.
273    pub time: Option<String>,
274    /// The login name of the engineer who created this build.
275    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/// Identifies the originating GPS device that tracked a run or
287/// used to identify the type of device capable of handling
288/// the data for loading.
289#[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/// The week is written out only if the notes are present.
476#[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    /// Non empty string up to 15 bytes
515    #[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// Activity Extensions
604
605#[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}