fit/transforms/
subfields.rs1use crate::profile::{FieldInfo, MesgInfo, SubField};
16use crate::transforms::components::scalar_as_u64;
17use crate::transforms::enum_strings::enum_str_by_value;
18use crate::RawMessage;
19
20pub fn select<'a>(
24 parent_field: &'a FieldInfo,
25 parent_mesg: &MesgInfo,
26 raw_message: &RawMessage<'_>,
27) -> Option<&'a SubField> {
28 if parent_field.sub_fields.is_empty() {
29 return None;
30 }
31 for sub in parent_field.sub_fields {
32 for (ref_name, expected) in sub.conditions {
33 if condition_matches(parent_mesg, raw_message, ref_name, expected) {
34 return Some(sub);
35 }
36 }
37 }
38 None
39}
40
41fn condition_matches(
42 parent_mesg: &MesgInfo,
43 raw_message: &RawMessage<'_>,
44 ref_field_name: &str,
45 expected: &str,
46) -> bool {
47 let Some(ref_field) = parent_mesg.field_by_name(ref_field_name) else {
49 return false;
50 };
51 let Some(actual) = raw_message.field(ref_field.field_def_num) else {
53 return false;
54 };
55 let Some(actual_u64) = scalar_as_u64(&actual.value) else {
56 return false;
57 };
58
59 if let Some(expected_int) = parse_int_literal(expected) {
63 return actual_u64 == expected_int;
64 }
65 if let Some(actual_str) = enum_str_by_value(ref_field.type_name, actual_u64) {
66 return actual_str == expected;
67 }
68 false
69}
70
71fn parse_int_literal(s: &str) -> Option<u64> {
72 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
73 return u64::from_str_radix(hex, 16).ok();
74 }
75 s.parse::<u64>().ok()
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::profile::generated::messages;
82 use crate::raw_value::RawValue;
83
84 #[test]
85 fn parse_int_literal_accepts_hex_and_decimal() {
86 assert_eq!(parse_int_literal("16"), Some(16));
87 assert_eq!(parse_int_literal("0x10"), Some(16));
88 assert_eq!(parse_int_literal("0X10"), Some(16));
89 assert_eq!(parse_int_literal("running"), None);
90 }
91
92 #[test]
93 fn file_id_product_subfield_resolves_for_garmin_manufacturer() {
94 let file_id = messages::ALL_MESSAGES
95 .iter()
96 .find(|m| m.name == "file_id")
97 .expect("file_id must exist");
98
99 let product_field = file_id
100 .field_by_name("product")
101 .expect("file_id.product must exist");
102 assert!(!product_field.sub_fields.is_empty());
103
104 let raw = RawMessage {
106 global_mesg_num: 0,
107 fields: vec![
108 crate::RawField {
109 field_def_num: 1, value: RawValue::U16Scalar(1),
111 },
112 crate::RawField {
113 field_def_num: 2, value: RawValue::U16Scalar(0),
115 },
116 ],
117 dev_fields: vec![],
118 starts_new_chain: false,
119 };
120
121 let sub = select(product_field, file_id, &raw)
122 .expect("garmin_product subfield must match for manufacturer=1");
123 assert_eq!(sub.name, "garmin_product");
124 }
125
126 #[test]
127 fn returns_none_when_no_condition_matches() {
128 let file_id = messages::ALL_MESSAGES
129 .iter()
130 .find(|m| m.name == "file_id")
131 .unwrap();
132 let product = file_id.field_by_name("product").unwrap();
133
134 let raw = RawMessage {
136 global_mesg_num: 0,
137 fields: vec![crate::RawField {
138 field_def_num: 1,
139 value: RawValue::U16Scalar(999),
140 }],
141 dev_fields: vec![],
142 starts_new_chain: false,
143 };
144 assert!(select(product, file_id, &raw).is_none());
145 }
146}