1use std::collections::HashMap;
7
8use crate::base_type::BaseType;
9
10#[derive(Debug, Clone)]
12pub struct DevFieldInfo {
13 pub name: String,
15 pub base_type: BaseType,
17 pub scale: Option<f64>,
19 pub offset: Option<f64>,
21 pub units: Option<String>,
23}
24
25type DevFieldKey = (u8, u8);
27
28#[derive(Debug, Default, Clone)]
31pub struct DevFieldRegistry {
32 fields: HashMap<DevFieldKey, DevFieldInfo>,
33}
34
35impl DevFieldRegistry {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 #[allow(clippy::too_many_arguments)]
44 pub fn register_field(
45 &mut self,
46 developer_data_index: u8,
47 field_definition_number: u8,
48 field_name: String,
49 fit_base_type_id: u8,
50 scale: Option<f64>,
51 offset: Option<f64>,
52 units: Option<String>,
53 ) {
54 let base_type = match BaseType::from_byte(fit_base_type_id & BaseType::TYPE_CODE_MASK) {
55 Ok(bt) => bt,
56 Err(_) => return, };
58 let key = (developer_data_index, field_definition_number);
59 self.fields.insert(
60 key,
61 DevFieldInfo {
62 name: field_name,
63 base_type,
64 scale,
65 offset,
66 units,
67 },
68 );
69 }
70
71 pub fn get(&self, developer_data_index: u8, field_def_num: u8) -> Option<&DevFieldInfo> {
73 self.fields.get(&(developer_data_index, field_def_num))
74 }
75
76 pub fn len(&self) -> usize {
78 self.fields.len()
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.fields.is_empty()
84 }
85}
86
87pub fn base_type_to_type_name(bt: BaseType) -> &'static str {
91 match bt {
92 BaseType::Enum => "enum",
93 BaseType::SInt8 => "sint8",
94 BaseType::UInt8 => "uint8",
95 BaseType::SInt16 => "sint16",
96 BaseType::UInt16 => "uint16",
97 BaseType::SInt32 => "sint32",
98 BaseType::UInt32 => "uint32",
99 BaseType::String => "string",
100 BaseType::Float32 => "float32",
101 BaseType::Float64 => "float64",
102 BaseType::UInt8z => "uint8z",
103 BaseType::UInt16z => "uint16z",
104 BaseType::UInt32z => "uint32z",
105 BaseType::Byte => "byte",
106 BaseType::SInt64 => "sint64",
107 BaseType::UInt64 => "uint64",
108 BaseType::UInt64z => "uint64z",
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn register_and_lookup() {
118 let mut reg = DevFieldRegistry::new();
119 reg.register_field(
120 0,
121 0,
122 "heart_rate".to_string(),
123 0x02, None,
125 None,
126 Some("bpm".to_string()),
127 );
128 let info = reg.get(0, 0).unwrap();
129 assert_eq!(info.name, "heart_rate");
130 assert_eq!(info.base_type, BaseType::UInt8);
131 assert_eq!(info.units.as_deref(), Some("bpm"));
132 }
133
134 #[test]
135 fn unknown_base_type_is_skipped() {
136 let mut reg = DevFieldRegistry::new();
137 reg.register_field(0, 0, "bad".to_string(), 0xFF, None, None, None);
138 assert!(reg.is_empty());
139 }
140
141 #[test]
142 fn base_type_to_type_name_round_trip() {
143 for code in 0x00..=0x10 {
144 let bt = BaseType::from_byte(code).unwrap();
145 let name = base_type_to_type_name(bt);
146 assert!(!name.is_empty());
147 }
148 }
149}