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
194define_transparent_wrapper!(
195    #[derive(Clone, Serialize, Default)]
196    #[serde(transparent)]
197    ColumnName: String
198);
199
200impl<'de> Deserialize<'de> for ColumnName {
201    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202    where
203        D: Deserializer<'de>,
204    {
205        let s = String::deserialize(deserializer)?;
206        if s.chars().all(|c| c.is_alphanumeric()) {
207            Ok(ColumnName(s))
208        } else {
209            Err(serde::de::Error::custom(format!(
210                "Invalid column name '{}': name must be alphanumeric",
211                s
212            )))
213        }
214    }
215}
216
217#[derive(Default, Debug, Clone, PartialEq, serde::Serialize)]
218pub struct ColumnSetting {
219    pub filter: bool,
220    pub hidden: bool,
221    pub name: ColumnName,
222}
223
224#[derive(Default, Debug, Clone)]
225pub enum Split {
226    /// Split by delimiter. Supports regex.
227    Delimiter(Regex),
228    /// A sequence of regexes.
229    Regexes(Vec<Regex>),
230    /// No splitting.
231    #[default]
232    None,
233}
234
235impl PartialEq for Split {
236    fn eq(&self, other: &Self) -> bool {
237        match (self, other) {
238            (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
239            (Split::Regexes(v1), Split::Regexes(v2)) => {
240                if v1.len() != v2.len() {
241                    return false;
242                }
243                v1.iter()
244                    .zip(v2.iter())
245                    .all(|(r1, r2)| r1.as_str() == r2.as_str())
246            }
247            (Split::None, Split::None) => true,
248            _ => false,
249        }
250    }
251}
252
253// ---------------------------------------------------------------------------------
254
255impl serde::Serialize for Split {
256    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
257    where
258        S: serde::Serializer,
259    {
260        match self {
261            Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
262            Split::Regexes(rs) => {
263                let mut seq = serializer.serialize_seq(Some(rs.len()))?;
264                for r in rs {
265                    seq.serialize_element(r.as_str())?;
266                }
267                seq.end()
268            }
269            Split::None => serializer.serialize_none(),
270        }
271    }
272}
273
274impl<'de> Deserialize<'de> for Split {
275    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
276    where
277        D: Deserializer<'de>,
278    {
279        struct SplitVisitor;
280
281        impl<'de> Visitor<'de> for SplitVisitor {
282            type Value = Split;
283
284            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
285                formatter.write_str("string for delimiter or array of strings for regexes")
286            }
287
288            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
289            where
290                E: de::Error,
291            {
292                // Try to compile single regex
293                Regex::new(value)
294                    .map(Split::Delimiter)
295                    .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
296            }
297
298            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
299            where
300                A: serde::de::SeqAccess<'de>,
301            {
302                let mut regexes = Vec::new();
303                while let Some(s) = seq.next_element::<String>()? {
304                    let r = Regex::new(&s)
305                        .map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
306                    regexes.push(r);
307                }
308                Ok(Split::Regexes(regexes))
309            }
310        }
311
312        deserializer.deserialize_any(SplitVisitor)
313    }
314}
315
316impl<'de> Deserialize<'de> for ColumnSetting {
317    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
318    where
319        D: Deserializer<'de>,
320    {
321        #[derive(Deserialize)]
322        #[serde(deny_unknown_fields)]
323        struct ColumnStruct {
324            #[serde(default = "default_true")]
325            filter: bool,
326            #[serde(default)]
327            hidden: bool,
328            name: ColumnName,
329        }
330
331        fn default_true() -> bool {
332            true
333        }
334
335        #[derive(Deserialize)]
336        #[serde(untagged)]
337        enum Input {
338            Str(ColumnName),
339            Obj(ColumnStruct),
340        }
341
342        match Input::deserialize(deserializer)? {
343            Input::Str(name) => Ok(ColumnSetting {
344                filter: true,
345                hidden: false,
346                name,
347            }),
348            Input::Obj(obj) => Ok(ColumnSetting {
349                filter: obj.filter,
350                hidden: obj.hidden,
351                name: obj.name,
352            }),
353        }
354    }
355}
356
357// ----------------------------------------------------------------------
358pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
359where
360    D: Deserializer<'de>,
361    T: From<String>,
362{
363    struct GenericVisitor<T> {
364        _marker: std::marker::PhantomData<T>,
365    }
366
367    impl<'de, T> Visitor<'de> for GenericVisitor<T>
368    where
369        T: From<String>,
370    {
371        type Value = T;
372
373        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
374            formatter.write_str("a string or single character")
375        }
376
377        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
378        where
379            E: de::Error,
380        {
381            let s = if v.chars().count() == 1 {
382                let mut s = String::with_capacity(2);
383                s.push(v.chars().next().unwrap());
384                s.push(' ');
385                s
386            } else {
387                v.to_string()
388            };
389            Ok(T::from(s))
390        }
391
392        fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
393        where
394            E: de::Error,
395        {
396            self.visit_str(&v)
397        }
398    }
399
400    deserializer.deserialize_string(GenericVisitor {
401        _marker: std::marker::PhantomData,
402    })
403}
404
405// ----------------------------------------------------------------------------
406define_transparent_wrapper!(
407    #[derive(Clone, Eq, Serialize)]
408    StringValue: String
409
410);
411
412impl<'de> Deserialize<'de> for StringValue {
413    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
414    where
415        D: Deserializer<'de>,
416    {
417        struct Visitor;
418
419        impl<'de> de::Visitor<'de> for Visitor {
420            type Value = StringValue;
421
422            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
423                formatter.write_str("a string, number, or bool")
424            }
425
426            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
427                Ok(StringValue(v.to_owned()))
428            }
429
430            fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
431                Ok(StringValue(v))
432            }
433
434            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
435                Ok(StringValue(v.to_string()))
436            }
437
438            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
439                Ok(StringValue(v.to_string()))
440            }
441
442            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
443                Ok(StringValue(v.to_string()))
444            }
445
446            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
447                Ok(StringValue(v.to_string()))
448            }
449        }
450
451        deserializer.deserialize_any(Visitor)
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[derive(Deserialize)]
460    struct TestName {
461        name: ColumnName,
462    }
463
464    #[test]
465    fn test_column_name_validation() {
466        // Valid
467        let name: TestName = toml::from_str("name = \"col1\"").expect("Valid name");
468        assert_eq!(name.name.as_str(), "col1");
469
470        let name: TestName = toml::from_str("name = \"Column123\"").expect("Valid name");
471        assert_eq!(name.name.as_str(), "Column123");
472
473        // Invalid
474        let res: Result<TestName, _> = toml::from_str("name = \"col-1\"");
475        assert!(res.is_err());
476
477        let res: Result<TestName, _> = toml::from_str("name = \"col 1\"");
478        assert!(res.is_err());
479
480        let res: Result<TestName, _> = toml::from_str("name = \"col_1\"");
481        assert!(res.is_err());
482    }
483}