Skip to main content

matchmaker/
config_types.rs

1use std::fmt;
2
3use cba::{bird::one_or_many, define_transparent_wrapper};
4use ratatui::widgets::Borders;
5
6use regex::Regex;
7
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{self, Visitor},
11    ser::SerializeSeq,
12};
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
15pub enum HorizontalSeparator {
16    #[default]
17    None,
18    Empty,
19    Light,
20    Normal,
21    Heavy,
22    Dashed,
23}
24
25impl HorizontalSeparator {
26    pub fn as_str(self) -> &'static str {
27        match self {
28            Self::None => unreachable!(),
29            Self::Empty => " ",
30            Self::Light => "─", // U+2500
31            Self::Normal => "─",
32            Self::Heavy => "━",  // U+2501
33            Self::Dashed => "╌", // U+254C (box drawings light double dash)
34        }
35    }
36}
37
38#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
39pub enum RowConnectionStyle {
40    #[default]
41    Disjoint,
42    Capped,
43    Full,
44}
45
46define_transparent_wrapper!(
47    #[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
48    #[serde(transparent)]
49    Count: u16 = 1
50);
51use ratatui::widgets::Padding as rPadding;
52
53define_transparent_wrapper!(
54    #[derive(Copy, Clone, Default)]
55    Padding: rPadding
56);
57
58#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
59#[serde(untagged)]
60pub enum ShowCondition {
61    Bool(bool),
62    Free(u16),
63}
64impl Default for ShowCondition {
65    fn default() -> Self {
66        Self::Bool(false)
67    }
68}
69impl From<bool> for ShowCondition {
70    fn from(value: bool) -> Self {
71        ShowCondition::Bool(value)
72    }
73}
74
75impl From<u16> for ShowCondition {
76    fn from(value: u16) -> Self {
77        ShowCondition::Free(value)
78    }
79}
80
81// -----------------------------------------------------------------------------------------
82
83impl Serialize for Padding {
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: Serializer,
87    {
88        use serde::ser::SerializeSeq;
89        let padding = self;
90        if padding.top == padding.bottom
91            && padding.left == padding.right
92            && padding.top == padding.left
93        {
94            serializer.serialize_u16(padding.top)
95        } else if padding.top == padding.bottom && padding.left == padding.right {
96            let mut seq = serializer.serialize_seq(Some(2))?;
97            seq.serialize_element(&padding.left)?;
98            seq.serialize_element(&padding.top)?;
99            seq.end()
100        } else {
101            let mut seq = serializer.serialize_seq(Some(4))?;
102            seq.serialize_element(&padding.top)?;
103            seq.serialize_element(&padding.right)?;
104            seq.serialize_element(&padding.bottom)?;
105            seq.serialize_element(&padding.left)?;
106            seq.end()
107        }
108    }
109}
110
111impl<'de> Deserialize<'de> for Padding {
112    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113    where
114        D: Deserializer<'de>,
115    {
116        use serde::de::Error;
117
118        let repr: Vec<u16> = one_or_many::deserialize(deserializer)?;
119
120        let inner = match repr.len() {
121            1 => {
122                let v = repr[0];
123                rPadding {
124                    top: v,
125                    right: v,
126                    bottom: v,
127                    left: v,
128                }
129            }
130            2 => {
131                let lr = repr[0];
132                let tb = repr[1];
133                rPadding {
134                    top: tb,
135                    right: lr,
136                    bottom: tb,
137                    left: lr,
138                }
139            }
140            4 => rPadding {
141                top: repr[0],
142                right: repr[1],
143                bottom: repr[2],
144                left: repr[3],
145            },
146            _ => {
147                return Err(D::Error::custom(
148                    "a number or an array of 1, 2, or 4 numbers",
149                ));
150            }
151        };
152
153        Ok(inner.into())
154    }
155}
156
157// ---------------------------------------------------------------------------------
158
159// define_restricted_wrapper!(
160//     #[derive(Clone, serde::Serialize, serde::Deserialize)]
161//     #[serde(transparent)]
162//     FormatString: String
163// );
164
165#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
166#[serde(rename_all = "lowercase")]
167pub enum Side {
168    Top,
169    Bottom,
170    Left,
171    #[default]
172    Right,
173}
174
175impl Side {
176    pub fn opposite(&self) -> Borders {
177        match self {
178            Side::Top => Borders::BOTTOM,
179            Side::Bottom => Borders::TOP,
180            Side::Left => Borders::RIGHT,
181            Side::Right => Borders::LEFT,
182        }
183    }
184}
185
186#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
187#[serde(rename_all = "lowercase")]
188pub enum CursorSetting {
189    None,
190    #[default]
191    Default,
192}
193
194#[derive(Debug, Clone, PartialEq, Serialize, Default)]
195#[serde(transparent)]
196pub struct ColumnName(String);
197
198impl<'de> Deserialize<'de> for ColumnName {
199    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200    where
201        D: Deserializer<'de>,
202    {
203        let s = String::deserialize(deserializer)?;
204        if s.chars().all(|c| c.is_alphanumeric()) {
205            Ok(ColumnName(s))
206        } else {
207            Err(serde::de::Error::custom(format!(
208                "Invalid column name '{}': name must be alphanumeric",
209                s
210            )))
211        }
212    }
213}
214
215impl ColumnName {
216    pub fn as_str(&self) -> &str {
217        &self.0
218    }
219}
220
221impl PartialEq<&str> for ColumnName {
222    fn eq(&self, other: &&str) -> bool {
223        self.0 == *other
224    }
225}
226
227impl From<String> for ColumnName {
228    fn from(s: String) -> Self {
229        ColumnName(s)
230    }
231}
232
233impl AsRef<str> for ColumnName {
234    fn as_ref(&self) -> &str {
235        &self.0
236    }
237}
238
239impl fmt::Display for ColumnName {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        write!(f, "{}", self.0)
242    }
243}
244
245#[derive(Default, Debug, Clone, PartialEq, serde::Serialize)]
246pub struct ColumnSetting {
247    pub filter: bool,
248    pub hidden: bool,
249    pub name: ColumnName,
250}
251
252#[derive(Default, Debug, Clone)]
253pub enum Split {
254    /// Split by delimiter. Supports regex.
255    Delimiter(Regex),
256    /// A sequence of regexes.
257    Regexes(Vec<Regex>),
258    /// No splitting.
259    #[default]
260    None,
261}
262
263impl PartialEq for Split {
264    fn eq(&self, other: &Self) -> bool {
265        match (self, other) {
266            (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
267            (Split::Regexes(v1), Split::Regexes(v2)) => {
268                if v1.len() != v2.len() {
269                    return false;
270                }
271                v1.iter()
272                    .zip(v2.iter())
273                    .all(|(r1, r2)| r1.as_str() == r2.as_str())
274            }
275            (Split::None, Split::None) => true,
276            _ => false,
277        }
278    }
279}
280
281// ---------------------------------------------------------------------------------
282
283impl serde::Serialize for Split {
284    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285    where
286        S: serde::Serializer,
287    {
288        match self {
289            Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
290            Split::Regexes(rs) => {
291                let mut seq = serializer.serialize_seq(Some(rs.len()))?;
292                for r in rs {
293                    seq.serialize_element(r.as_str())?;
294                }
295                seq.end()
296            }
297            Split::None => serializer.serialize_none(),
298        }
299    }
300}
301
302impl<'de> Deserialize<'de> for Split {
303    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304    where
305        D: Deserializer<'de>,
306    {
307        struct SplitVisitor;
308
309        impl<'de> Visitor<'de> for SplitVisitor {
310            type Value = Split;
311
312            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
313                formatter.write_str("string for delimiter or array of strings for regexes")
314            }
315
316            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
317            where
318                E: de::Error,
319            {
320                // Try to compile single regex
321                Regex::new(value)
322                    .map(Split::Delimiter)
323                    .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
324            }
325
326            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
327            where
328                A: serde::de::SeqAccess<'de>,
329            {
330                let mut regexes = Vec::new();
331                while let Some(s) = seq.next_element::<String>()? {
332                    let r = Regex::new(&s)
333                        .map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
334                    regexes.push(r);
335                }
336                Ok(Split::Regexes(regexes))
337            }
338        }
339
340        deserializer.deserialize_any(SplitVisitor)
341    }
342}
343
344impl<'de> Deserialize<'de> for ColumnSetting {
345    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
346    where
347        D: Deserializer<'de>,
348    {
349        #[derive(Deserialize)]
350        #[serde(deny_unknown_fields)]
351        struct ColumnStruct {
352            #[serde(default = "default_true")]
353            filter: bool,
354            #[serde(default)]
355            hidden: bool,
356            name: ColumnName,
357        }
358
359        fn default_true() -> bool {
360            true
361        }
362
363        #[derive(Deserialize)]
364        #[serde(untagged)]
365        enum Input {
366            Str(ColumnName),
367            Obj(ColumnStruct),
368        }
369
370        match Input::deserialize(deserializer)? {
371            Input::Str(name) => Ok(ColumnSetting {
372                filter: true,
373                hidden: false,
374                name,
375            }),
376            Input::Obj(obj) => Ok(ColumnSetting {
377                filter: obj.filter,
378                hidden: obj.hidden,
379                name: obj.name,
380            }),
381        }
382    }
383}
384
385// ----------------------------------------------------------------------
386pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
387where
388    D: Deserializer<'de>,
389    T: From<String>,
390{
391    struct GenericVisitor<T> {
392        _marker: std::marker::PhantomData<T>,
393    }
394
395    impl<'de, T> Visitor<'de> for GenericVisitor<T>
396    where
397        T: From<String>,
398    {
399        type Value = T;
400
401        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
402            formatter.write_str("a string or single character")
403        }
404
405        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
406        where
407            E: de::Error,
408        {
409            let s = if v.chars().count() == 1 {
410                let mut s = String::with_capacity(2);
411                s.push(v.chars().next().unwrap());
412                s.push(' ');
413                s
414            } else {
415                v.to_string()
416            };
417            Ok(T::from(s))
418        }
419
420        fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
421        where
422            E: de::Error,
423        {
424            self.visit_str(&v)
425        }
426    }
427
428    deserializer.deserialize_string(GenericVisitor {
429        _marker: std::marker::PhantomData,
430    })
431}
432
433// ----------------------------------------------------------------------------
434define_transparent_wrapper!(
435    #[derive(Clone, Eq, Serialize)]
436    StringValue: String
437
438);
439
440impl<'de> Deserialize<'de> for StringValue {
441    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442    where
443        D: Deserializer<'de>,
444    {
445        struct Visitor;
446
447        impl<'de> de::Visitor<'de> for Visitor {
448            type Value = StringValue;
449
450            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
451                formatter.write_str("a string, number, or bool")
452            }
453
454            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
455                Ok(StringValue(v.to_owned()))
456            }
457
458            fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
459                Ok(StringValue(v))
460            }
461
462            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
463                Ok(StringValue(v.to_string()))
464            }
465
466            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
467                Ok(StringValue(v.to_string()))
468            }
469
470            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
471                Ok(StringValue(v.to_string()))
472            }
473
474            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
475                Ok(StringValue(v.to_string()))
476            }
477        }
478
479        deserializer.deserialize_any(Visitor)
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    #[derive(Deserialize)]
488    struct TestName {
489        name: ColumnName,
490    }
491
492    #[test]
493    fn test_column_name_validation() {
494        // Valid
495        let name: TestName = toml::from_str("name = \"col1\"").expect("Valid name");
496        assert_eq!(name.name.as_ref(), "col1");
497
498        let name: TestName = toml::from_str("name = \"Column123\"").expect("Valid name");
499        assert_eq!(name.name.as_ref(), "Column123");
500
501        // Invalid
502        let res: Result<TestName, _> = toml::from_str("name = \"col-1\"");
503        assert!(res.is_err());
504
505        let res: Result<TestName, _> = toml::from_str("name = \"col 1\"");
506        assert!(res.is_err());
507
508        let res: Result<TestName, _> = toml::from_str("name = \"col_1\"");
509        assert!(res.is_err());
510    }
511
512    #[derive(Deserialize)]
513    struct TestSetting {
514        setting: ColumnSetting,
515    }
516
517    #[test]
518    fn test_column_setting_deserialization() {
519        // From string
520        let setting: TestSetting = toml::from_str("setting = \"col1\"").expect("Valid name");
521        assert_eq!(setting.setting.name.as_ref(), "col1");
522        assert!(setting.setting.filter);
523        assert!(!setting.setting.hidden);
524
525        // From object
526        let setting: TestSetting = toml::from_str("setting = { name = \"col2\", filter = false }").expect("Valid object");
527        assert_eq!(setting.setting.name.as_ref(), "col2");
528        assert!(!setting.setting.filter);
529
530        // Invalid name in object
531        let res: Result<TestSetting, _> = toml::from_str("setting = { name = \"col-2\" }");
532        assert!(res.is_err());
533    }
534}