1use std::collections::HashMap;
21use std::sync::LazyLock;
22
23use regex::Regex;
24use serde_json::Value;
25
26use crate::error::RadioErrors;
27
28#[derive(Debug, Clone)]
30pub struct RadioModel {
31 pub name: String,
33
34 pub serial_max_len: usize,
36
37 _serial_regex_patterns: HashMap<String, String>,
39
40 pub extra_max_len: usize,
42
43 _extra_regex_patterns: Option<HashMap<String, String>>,
45
46 pub default_programming_language: String,
48}
49
50impl RadioModel {
51 pub fn regex_string_to_rule(regex_string: &str) -> Result<Regex, regex::Error> {
56 let s = regex_string.trim();
57 if !s.starts_with('/') {
58 return Regex::new(s);
59 }
60 let inner = &s[1..];
61 let Some(last_slash) = inner.rfind('/') else {
62 return Regex::new(s);
63 };
64 let (pattern_src, flags) = inner.split_at(last_slash);
65 let flags = &flags[1..];
66 let mut b = regex::RegexBuilder::new(pattern_src);
67 if flags.contains('i') {
68 b.case_insensitive(true);
69 }
70 b.build()
71 }
72
73 pub fn serial_regex_pattern(&self) -> String {
77 self._serial_regex_patterns
78 .get(&self.default_programming_language)
79 .cloned()
80 .unwrap_or_default()
81 }
82
83 pub fn extra_regex_pattern(&self) -> Option<String> {
87 let map = self._extra_regex_patterns.as_ref()?;
88 map.get(&self.default_programming_language).cloned()
89 }
90
91 pub fn new(
99 name: impl Into<String>,
100 serial_max_len: usize,
101 serial_regex_pattern: Value,
102 extra_max_len: usize,
103 extra_regex_pattern: Option<Value>,
104 ) -> Self {
105 let name = name.into();
106 let mut _serial_regex_patterns: HashMap<String, String> = HashMap::new();
108
109 let default_programming_language = "js".to_string();
110
111 match &serial_regex_pattern {
113 Value::String(s) => {
114 _serial_regex_patterns.insert(default_programming_language.clone(), s.clone());
115 }
116 Value::Object(map) => {
117 for (k, v) in map {
118 if let Some(s) = v.as_str() {
119 _serial_regex_patterns.insert(k.clone(), s.to_string());
120 }
121 }
122 }
123 _ => {}
124 }
125
126 let mut _extra_regex_patterns: Option<HashMap<String, String>> = None;
128
129 if extra_max_len != 0 {
130 let mut map = HashMap::new();
131 match extra_regex_pattern {
132 Some(Value::String(s)) => {
133 map.insert(default_programming_language.clone(), s);
134 }
135 Some(Value::Object(o)) => {
136 for (k, v) in o {
137 if let Some(s) = v.as_str() {
138 map.insert(k.clone(), s.to_string());
139 }
140 }
141 }
142 _ => {}
143 }
144 _extra_regex_patterns = Some(map);
145 }
146
147 Self {
148 name,
149 serial_max_len,
150 _serial_regex_patterns,
151 extra_max_len,
152 _extra_regex_patterns,
153 default_programming_language,
154 }
155 }
156
157 pub fn with_slash_patterns(
159 name: impl Into<String>,
160 serial_max_len: usize,
161 serial_regex: &str,
162 extra_max_len: usize,
163 extra_regex: Option<&str>,
164 ) -> Self {
165 Self::new(
166 name,
167 serial_max_len,
168 Value::String(serial_regex.to_string()),
169 extra_max_len,
170 extra_regex.map(|s| Value::String(s.to_string())),
171 )
172 }
173
174 pub fn validate(&self, serial: &str, extra: Option<&str>) -> i32 {
180 if serial.len() != self.serial_max_len {
181 return RadioErrors::INVALID_SERIAL_LENGTH;
182 }
183
184 let serial_pat = self.serial_regex_pattern();
185 if serial_pat.is_empty() {
186 return RadioErrors::INVALID_SERIAL_PATTERN;
187 }
188 let Ok(re) = Self::regex_string_to_rule(&serial_pat) else {
189 return RadioErrors::INVALID_SERIAL_PATTERN;
190 };
191 if !re.is_match(serial) {
192 return RadioErrors::INVALID_SERIAL_PATTERN;
193 }
194
195 if let Some(ex) = extra {
196 if !ex.is_empty() {
197 if ex.len() != self.extra_max_len {
198 return RadioErrors::INVALID_EXTRA_LENGTH;
199 }
200 let Some(extra_pat) = self.extra_regex_pattern() else {
201 return RadioErrors::INVALID_EXTRA_PATTERN;
202 };
203 let Ok(re_extra) = Self::regex_string_to_rule(&extra_pat) else {
204 return RadioErrors::INVALID_EXTRA_PATTERN;
205 };
206 if !re_extra.is_match(ex) {
207 return RadioErrors::INVALID_EXTRA_PATTERN;
208 }
209 }
210 }
211
212 RadioErrors::SUCCESS
213 }
214}
215
216#[allow(non_snake_case)]
225pub mod RadioModels {
226 use super::{LazyLock, RadioModel};
227
228 macro_rules! lazy_model {
229 ($name:ident, $slug:literal, $len:expr, $pat:literal) => {
230 pub static $name: LazyLock<RadioModel> =
232 LazyLock::new(|| RadioModel::with_slash_patterns($slug, $len, $pat, 0, None));
233 };
234 }
235
236 lazy_model!(RENAULT_DACIA, "renault-dacia", 4, "/^([A-Z]{1}[0-9]{3})$/");
237 lazy_model!(
238 CHRYSLER_PANASONIC_TM9,
239 "chrysler-panasonic-tm9",
240 4,
241 "/^([0-9]{4})$/"
242 );
243 lazy_model!(
244 CHRYSLER_DODGE_VP,
245 "chrysler-dodge-vp",
246 4,
247 "/^([a-zA-Z0-9]{4})$/"
248 );
249 lazy_model!(FORD_M_SERIES, "ford-m-series", 6, "/^([0-9]{6})$/");
250 lazy_model!(FORD_V_SERIES, "ford-v-series", 6, "/^([0-9]{6})$/");
251 lazy_model!(FORD_TRAVELPILOT, "ford-travelpilot", 7, "/^([0-9]{7})$/");
252 lazy_model!(
253 FIAT_STILO_BRAVO_VISTEON,
254 "fiat-stilo-bravo-visteon",
255 6,
256 "/^([a-zA-Z0-9]{6})$/"
257 );
258 lazy_model!(FIAT_DAIICHI, "fiat-daiichi", 4, "/^([0-9]{4})$/");
259 lazy_model!(FIAT_VP, "fiat-vp", 4, "/^([0-9]{4})$/");
260 lazy_model!(TOYOTA_ERC, "toyota-erc", 16, "/^([a-zA-Z0-9]{16})$/");
261 lazy_model!(
262 JEEP_CHEROKEE,
263 "jeep-cherokee",
264 14,
265 "/^([a-zA-Z0-9]{10}[0-9]{4})$/"
266 );
267 lazy_model!(
268 NISSAN_GLOVE_BOX,
269 "nissan-glove-box",
270 12,
271 "/^([a-zA-Z0-9]{12})$/"
272 );
273 lazy_model!(ECLIPSE_ESN, "eclipse-esn", 6, "/^([a-zA-Z0-9]{6})$/");
274 lazy_model!(JAGUAR_ALPINE, "jaguar-alpine", 5, "/^([0-9]{5})$/");
275}