libsubconverter/settings/
yaml_deserializer.rs

1use std::{collections::HashMap, fmt};
2
3use serde::{
4    de::{MapAccess, SeqAccess, Visitor},
5    Deserialize,
6};
7
8use super::settings::yaml_settings::TemplateSettings;
9
10/// Stream rule configuration
11#[derive(Debug, Clone, Deserialize, Default)]
12#[serde(default)]
13pub struct RegexMatchRuleInYaml {
14    #[serde(rename = "match")]
15    pub match_str: Option<String>,
16    pub replace: Option<String>,
17    pub script: Option<String>,
18    pub import: Option<String>,
19}
20
21/// Trait for converting to INI format with a specified delimiter
22pub trait ToIniWithDelimiter {
23    fn to_ini_with_delimiter(&self, delimiter: &str) -> String;
24}
25
26impl ToIniWithDelimiter for RegexMatchRuleInYaml {
27    fn to_ini_with_delimiter(&self, delimiter: &str) -> String {
28        // Check for script first
29        if let Some(script) = &self.script {
30            if !script.is_empty() {
31                return format!("!!script:{}", script);
32            }
33        }
34
35        // Then check for import
36        if let Some(import) = &self.import {
37            if !import.is_empty() {
38                return format!("!!import:{}", import);
39            }
40        }
41
42        // Finally check for match and replace
43        if let (Some(match_str), Some(replace)) = (&self.match_str, &self.replace) {
44            if !match_str.is_empty() && !replace.is_empty() {
45                return format!("{}{}{}", match_str, delimiter, replace);
46            }
47        }
48
49        // Default to empty string if nothing matches
50        String::new()
51    }
52}
53
54pub trait ToIni {
55    fn to_ini(&self) -> String;
56}
57
58impl ToIni for RulesetConfigInYaml {
59    fn to_ini(&self) -> String {
60        // Check for import first
61        if let Some(import) = &self.import {
62            if !import.is_empty() {
63                return format!("!!import:{}", import);
64            }
65        }
66
67        // Then check for ruleset URL
68        if let Some(ruleset) = &self.ruleset {
69            if !ruleset.is_empty() {
70                let mut result = format!("{},{}", self.group, ruleset);
71                // Add interval if provided
72                if let Some(interval) = self.interval {
73                    result = format!("{},{}", result, interval);
74                }
75                return result;
76            }
77        }
78
79        // Finally check for rule
80        if let Some(rule) = &self.rule {
81            if !rule.is_empty() {
82                return format!("{},[]{}", self.group, rule);
83            }
84        }
85
86        // Default to empty string if nothing matches
87        String::new()
88    }
89}
90
91impl ToIni for TaskConfigInYaml {
92    fn to_ini(&self) -> String {
93        // Check for import first
94        if let Some(import) = &self.import {
95            if !import.is_empty() {
96                return format!("!!import:{}", import);
97            }
98        }
99
100        // Otherwise join fields with backticks
101        format!(
102            "{}`{}`{}`{}",
103            self.name, self.cronexp, self.path, self.timeout
104        )
105    }
106}
107
108/// Proxy group configuration
109#[derive(Debug, Clone, Deserialize, Default)]
110#[serde(default)]
111pub struct ProxyGroupConfigInYaml {
112    pub name: String,
113    #[serde(rename = "type")]
114    pub group_type: String,
115    pub rule: Vec<String>,
116    #[serde(default = "default_test_url")]
117    pub url: Option<String>,
118    #[serde(default = "default_interval")]
119    pub interval: Option<u32>,
120    pub tolerance: Option<u32>,
121    pub timeout: Option<u32>,
122    pub import: Option<String>,
123}
124
125impl ToIni for ProxyGroupConfigInYaml {
126    fn to_ini(&self) -> String {
127        // Check for import first
128        if let Some(import) = &self.import {
129            if !import.is_empty() {
130                return format!("!!import:{}", import);
131            }
132        }
133
134        // Create initial array with name and type
135        let mut temp_array = vec![self.name.clone(), self.group_type.clone()];
136
137        // Add all rules
138        for rule in &self.rule {
139            temp_array.push(rule.clone());
140        }
141
142        // Check if we have enough elements based on group type
143        match self.group_type.as_str() {
144            "select" => {
145                if temp_array.len() < 3 {
146                    return String::new();
147                }
148            }
149            "ssid" => {
150                if temp_array.len() < 4 {
151                    return String::new();
152                }
153            }
154            _ => {
155                if temp_array.len() < 3 {
156                    return String::new();
157                }
158
159                // Add url
160                temp_array.push(
161                    self.url
162                        .clone()
163                        .unwrap_or_else(|| "http://www.gstatic.com/generate_204".to_string()),
164                );
165
166                // Add interval, timeout, tolerance as a combined string
167                let interval = self.interval.unwrap_or(300).to_string();
168                let timeout = match self.timeout {
169                    Some(t) => t.to_string(),
170                    None => String::new(),
171                };
172                let tolerance = match self.tolerance {
173                    Some(t) => t.to_string(),
174                    None => String::new(),
175                };
176
177                temp_array.push(format!("{},{},{}", interval, timeout, tolerance));
178            }
179        }
180
181        // Join all elements with backtick
182        temp_array.join("`")
183    }
184}
185
186fn default_test_url() -> Option<String> {
187    Some("http://www.gstatic.com/generate_204".to_string())
188}
189
190fn default_interval() -> Option<u32> {
191    Some(300)
192}
193
194/// Task configuration
195#[derive(Debug, Clone, Deserialize, Default)]
196#[serde(default)]
197pub struct TaskConfigInYaml {
198    pub name: String,
199    pub cronexp: String,
200    pub path: String,
201    pub timeout: u32,
202    pub import: Option<String>,
203}
204
205/// Ruleset configuration
206#[derive(Debug, Clone, Deserialize, Default)]
207#[serde(default)]
208pub struct RulesetConfigInYaml {
209    pub rule: Option<String>,
210    pub ruleset: Option<String>,
211    pub group: String,
212    pub interval: Option<u32>,
213    pub import: Option<String>,
214}
215
216pub fn deserialize_template_as_template_settings<'de, D>(
217    deserializer: D,
218) -> Result<TemplateSettings, D::Error>
219where
220    D: serde::Deserializer<'de>,
221{
222    struct TemplateSettingsVisitor;
223
224    #[derive(Debug, Clone, Deserialize, Default)]
225    struct TemplateGlobalsVariable {
226        key: String,
227        #[serde(deserialize_with = "deserialize_as_string")]
228        value: String,
229    }
230
231    impl<'de> Visitor<'de> for TemplateSettingsVisitor {
232        type Value = TemplateSettings;
233
234        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
235            formatter.write_str("a TemplateSettings struct")
236        }
237
238        fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
239        where
240            V: MapAccess<'de>,
241        {
242            let mut template_settings = TemplateSettings::default();
243            while let Some(key) = map.next_key::<String>()? {
244                if key == "template_path" {
245                    let value = map.next_value::<String>()?;
246                    template_settings.template_path = value.clone();
247                } else if key == "globals" {
248                    let value = map.next_value::<Vec<TemplateGlobalsVariable>>()?;
249                    for item in value {
250                        template_settings.globals.insert(item.key, item.value);
251                    }
252                }
253            }
254            Ok(template_settings)
255        }
256    }
257
258    deserializer.deserialize_any(TemplateSettingsVisitor)
259}
260
261/// Template argument
262#[derive(Debug, Clone, Deserialize, Default)]
263struct TemplateArgument {
264    pub key: String,
265    pub value: String,
266}
267
268pub fn deserialize_template_args_as_hash_map<'de, D>(
269    deserializer: D,
270) -> Result<Option<HashMap<String, String>>, D::Error>
271where
272    D: serde::Deserializer<'de>,
273{
274    struct TemplateArgsVisitor;
275
276    impl<'de> Visitor<'de> for TemplateArgsVisitor {
277        type Value = Option<HashMap<String, String>>;
278
279        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
280            formatter.write_str("a sequence of template arguments or null")
281        }
282
283        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
284        where
285            S: SeqAccess<'de>,
286        {
287            let mut template_args = HashMap::new();
288
289            while let Some(item) = seq.next_element::<TemplateArgument>()? {
290                template_args.insert(item.key, item.value);
291            }
292
293            if template_args.is_empty() {
294                Ok(None)
295            } else {
296                Ok(Some(template_args))
297            }
298        }
299
300        fn visit_none<E>(self) -> Result<Self::Value, E>
301        where
302            E: serde::de::Error,
303        {
304            Ok(None)
305        }
306
307        fn visit_unit<E>(self) -> Result<Self::Value, E>
308        where
309            E: serde::de::Error,
310        {
311            Ok(None)
312        }
313
314        fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
315        where
316            M: MapAccess<'de>,
317        {
318            let mut template_args = HashMap::new();
319
320            while let Some((key, value)) = map.next_entry::<String, String>()? {
321                template_args.insert(key, value);
322            }
323
324            if template_args.is_empty() {
325                Ok(None)
326            } else {
327                Ok(Some(template_args))
328            }
329        }
330    }
331
332    deserializer.deserialize_any(TemplateArgsVisitor)
333}
334
335pub fn deserialize_as_string<'de, D>(deserializer: D) -> Result<String, D::Error>
336where
337    D: serde::Deserializer<'de>,
338{
339    struct StringVisitor;
340
341    impl<'de> Visitor<'de> for StringVisitor {
342        type Value = String;
343
344        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
345            formatter.write_str("a string, number, or boolean")
346        }
347
348        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
349        where
350            E: serde::de::Error,
351        {
352            Ok(value.to_string())
353        }
354
355        fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
356        where
357            E: serde::de::Error,
358        {
359            Ok(value)
360        }
361
362        fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
363        where
364            E: serde::de::Error,
365        {
366            Ok(value.to_string())
367        }
368
369        fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
370        where
371            E: serde::de::Error,
372        {
373            Ok(value.to_string())
374        }
375
376        fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
377        where
378            E: serde::de::Error,
379        {
380            Ok(value.to_string())
381        }
382
383        fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
384        where
385            E: serde::de::Error,
386        {
387            Ok(value.to_string())
388        }
389
390        fn visit_none<E>(self) -> Result<Self::Value, E>
391        where
392            E: serde::de::Error,
393        {
394            Ok(String::new())
395        }
396
397        fn visit_unit<E>(self) -> Result<Self::Value, E>
398        where
399            E: serde::de::Error,
400        {
401            Ok(String::new())
402        }
403    }
404
405    deserializer.deserialize_any(StringVisitor)
406}