arcs_ctf_yaml_parser/
correctness.rs

1use std::borrow::Cow;
2use std::fmt::{Debug, Display};
3use std::sync::Arc;
4use regex::Regex;
5
6use crate::YamlShape;
7use crate::categories::structs::Category;
8
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct YamlCorrectness {
12    flag: FlagCorrectness,
13    categories: CategoryCorrectness,
14    points: PointCorrectness,
15}
16
17#[derive(Debug, Clone)]
18pub enum FlagCorrectness {
19    None,
20    CompName(Cow<'static, str>),
21    Regex(Regex),
22}
23impl PartialEq for FlagCorrectness {
24    fn eq(&self, other: &Self) -> bool {
25        use FlagCorrectness::{CompName, None, Regex};
26        match (self, other) {
27            (None, None) => true,
28            (CompName(n1), CompName(n2)) => n1 == n2,
29            (Regex(r1), Regex(r2)) => r1.as_str() == r2.as_str(),
30            _ => false,
31        }
32    }
33}
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum CategoryCorrectness {
37    AnyStr,
38    List {
39        names: Cow<'static, [Cow<'static, str>]>,
40        requires_case_match: bool,
41    }
42}
43
44pub trait CanBePred: Fn(u64) -> bool + Debug + Send + Sync {}
45
46#[derive(Clone)]
47pub enum PointCorrectness {
48    None,
49    Multiple(u64),
50    Pred(Arc<dyn CanBePred>),
51}
52
53impl PartialEq for PointCorrectness {
54    fn eq(&self, other: &Self) -> bool {
55        use PointCorrectness::{Multiple, None, Pred};
56        match (self, other) {
57            (None, None) => true,
58            (Multiple(n1), Multiple(n2)) => n1 == n2,
59            (Pred(p1), Pred(p2)) => std::ptr::eq(
60                Arc::as_ptr(p1) as *mut (),
61                Arc::as_ptr(p2) as *mut (),
62            ),
63            _ => false,
64        }
65    }
66}
67impl Debug for PointCorrectness {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            Self::None => write!(f, "None"),
71            Self::Multiple(n) => write!(f, "Multiple< of {n} >"),
72            Self::Pred(_) => write!(f, "Predicate< unknown >"),
73        }
74    }
75}
76
77impl YamlCorrectness {
78    pub fn check_flag(&self, flag: &str) -> bool { self.flag.check(flag) }
79    pub fn check_cats<'a>(&self, categories: impl Iterator<Item = &'a str>) -> bool { self.categories.check(categories) }
80    pub fn check_pnts(&self, points: u64) -> bool { self.points.check(points) }
81
82    pub fn verify<'a>(&self, shape: &'a YamlShape) -> Result<&'a YamlShape, YamlCorrectness> {
83        let flag_ok = self.check_flag(shape.flag.as_str());
84        let cats_ok = self.check_cats(shape.categories.iter().map(Category::as_str));
85        let pnts_ok = self.check_pnts(shape.points);
86        if flag_ok && cats_ok && pnts_ok {
87            Ok(shape)
88        } else {
89            Err(Self {
90                flag: if flag_ok { FlagCorrectness::None } else { self.flag.clone() },
91                categories: if cats_ok { CategoryCorrectness::AnyStr } else { self.categories.clone() },
92                points: if pnts_ok { PointCorrectness::None } else { self.points.clone() },
93            })
94        }
95    }
96}
97
98impl FlagCorrectness {
99    pub fn check(&self, flag: &str) -> bool {
100        match self {
101            Self::None => true,
102            Self::CompName(name) => {
103                if let Some(flag) = flag.strip_prefix(name.as_ref()) {
104                    if let Some(flag) = flag.strip_prefix('{') {
105                        flag.ends_with('}')
106                    } else { false }
107                } else { false }
108            },
109            Self::Regex(regex) => regex.is_match(flag),
110        }
111    }
112}
113
114impl CategoryCorrectness {
115    pub fn check<'a>(&self, mut categories: impl Iterator<Item = &'a str>) -> bool {
116        match self {
117            Self::AnyStr => true,
118            Self::List { names, requires_case_match } => {
119                let case_match_predicate = |a: &str, b: &str| if *requires_case_match {
120                    a == b
121                } else {
122                    a.to_lowercase() == b.to_lowercase()
123                };
124                
125                let pred = |check| names
126                    .iter()
127                    .any(|valid| case_match_predicate(valid, check));
128
129                categories.all(pred)
130            },
131        }
132    }
133}
134
135impl PointCorrectness {
136    pub fn check(&self, num: u64) -> bool {
137        match self {
138            Self::None => true,
139            Self::Multiple(factor) => num % factor == 0,
140            Self::Pred(pred) => pred(num),
141        }
142    }
143}
144
145impl Default for YamlCorrectness {
146    fn default() -> Self {
147        Self {
148            flag: FlagCorrectness::None,
149            categories: CategoryCorrectness::AnyStr,
150            points: PointCorrectness::None,
151        }
152    }
153}
154
155
156impl YamlCorrectness {
157    pub fn with_flag(self, flag: FlagCorrectness) -> Self { Self { flag, ..self } }
158    pub fn with_cats(self, categories: CategoryCorrectness) -> Self { Self { categories, ..self } }
159    pub fn with_pnts(self, points: PointCorrectness) -> Self { Self { points, ..self } }
160}
161
162
163impl YamlCorrectness {
164    pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        writeln!(f)?;
166        if !matches!(self.flag, FlagCorrectness::None) {
167            write!(f, "    ")?;
168            self.flag.show_issue(f)?;
169        }
170        if !matches!(self.categories, CategoryCorrectness::AnyStr) {
171            write!(f, "    ")?;
172            self.categories.show_issue(f)?;
173        }
174        if !matches!(self.points, PointCorrectness::None) {
175            write!(f, "    ")?;
176            self.points.show_issue(f)?;
177        }
178
179        Ok(())
180    }
181}
182impl FlagCorrectness {
183    pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        use FlagCorrectness::*;
185        match self {
186            None => Ok(()),
187            CompName(name) => writeln!(f, "The flag should be in the format of: `{name}{{<contents>}}`"),
188            Regex(regex) => writeln!(f, "The flag must match the regex: `{}`", regex.as_str()),
189        }
190    }
191}
192impl CategoryCorrectness {
193    pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        use CategoryCorrectness::*;
195        match self {
196            AnyStr => Ok(()),
197            List {
198                names,
199                requires_case_match
200            } => {
201                write!(f, "The categories should be one of {names:?} (case ")?;
202                if *requires_case_match {
203                    writeln!(f, "SENSITIVE)")
204                } else {
205                    writeln!(f, "insensitive)")
206                }
207            },
208        }
209    }
210}
211impl PointCorrectness {
212    pub fn show_issue(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        use PointCorrectness::*;
214        match self {
215            None => Ok(()),
216            Multiple(n) => writeln!(f, "The point value MUST be multiple of {n}"),
217            Pred(pred) => writeln!(f, "The flag must pass a predicate: `{pred:?}`"),
218        }
219    }
220}
221
222
223impl Display for YamlCorrectness {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        self.show_issue(f)
226    }
227}