arcs_ctf_yaml_parser/
lib.rs

1pub mod categories;
2pub mod lists;
3pub mod flag;
4pub mod files;
5pub mod deploy;
6
7mod structs;
8mod accessors;
9mod serialize_impl;
10
11pub mod correctness;
12
13
14use std::path::Path;
15
16// Serde
17use serde_yaml::{
18    Mapping as YamlMapping,
19    Value as YamlValue,
20};
21
22
23// Parsing functions, types, and errors
24use {
25    categories::value_to_categories,
26    flag::get_flag,
27    files::file_list,
28    lists::as_str_list,
29    deploy::parse_deploy,
30};
31
32use {
33    files::structs::Files,
34    flag::Flag,
35    lists::structs::{ Authors, Hints },
36    categories::Categories,
37};
38pub use deploy::structs::DeployOptions;
39pub use files::structs::File;
40
41// Yaml ValueType stuff
42use structs::{
43    ValueType,
44    get_type,
45};
46
47
48// Verification stuff
49pub use structs::{
50    YamlVerifyError,
51    YamlAttribVerifyError
52};
53use correctness::YamlCorrectness;
54
55
56
57
58
59#[derive(PartialEq, Debug)]
60pub struct YamlShape {
61    authors: Authors,
62    categories: Categories,
63    hints: Hints,
64    files: Option<Files>,
65
66    deploy: Option<DeployOptions>,
67
68    points: u64,
69    flag: Flag,
70    
71    name: String,
72    description: String,
73
74    visible: bool,
75}
76
77
78
79macro_rules! collect_errors {
80    ($($vals:ident),+ $(,)?) => {
81        collect_errors!(@impl left: $($vals,)+; good: []; errors: [])
82    };
83    (@impl left: $val:ident, $($next_vals:ident,)*; good: [$($good_exprs:expr,)*]; errors: [$($err_exprs:expr,)*]) => {
84        match &$val {
85            Ok(_)  => collect_errors!(@impl left: $($next_vals,)*; good: [$($good_exprs,)* $val.unwrap(),]; errors: [$($err_exprs,)*]),
86            Err(_) => collect_errors!(@impl left: $($next_vals,)*; good: [$($good_exprs,)*]; errors: [$($err_exprs,)* $val.unwrap_err(),]),
87        }
88    };
89    (@impl left: ; good: [$($good_exprs:expr,)*]; errors: []) => {
90        Ok(($($good_exprs,)*))
91    };
92    (@impl left: ; good: [$($good_exprs:expr,)*]; errors: [$($err_exprs:expr,)*]) => {
93        Err(vec![$($err_exprs,)*])
94    };
95}
96
97macro_rules! get_map {
98    ($base:ident.$key:ident, $map:expr, missing: $err:expr $(,)?) => {
99        $base
100            .get(stringify!($key))
101            .map_or(Err($err), $map)
102    };
103    ($base:ident.$key:ident, $map:expr, $(default $(,)?)?) => {
104        $base
105            .get(stringify!($key))
106            .map_or_else(|| Err(Default::default()), $map)
107    };
108    ($base:ident.$key:ident, $map:expr, missing: $err:expr, error_wrap: $err_mapper:expr $(,)?) => {
109        $base
110            .get(stringify!($key))
111            .map_or(Err($err), $map)
112            .map_err($err_mapper)
113    };
114}
115
116macro_rules! get_primitive {
117    ($base:ident.$key:ident ($fn:ident $(=> $map:expr)?) else $err:expr) => {
118        if let Some(val) = $base.get(stringify!($key)) {
119            val.$fn()$(.map($map))?.ok_or_else(|| $err(get_type(val)))
120        } else { Err($err(ValueType::NULL)) }
121    };
122}
123
124fn verify_yaml(yaml_text: &str, correctness_options: Option<YamlCorrectness>, base_path: &Path) -> Result<YamlShape, YamlVerifyError> {
125    use YamlVerifyError::*;
126    use YamlAttribVerifyError::*;
127    use YamlAttribVerifyError as AttribError;
128
129    let correctness = correctness_options.unwrap_or_default();
130
131    let base: YamlValue = serde_yaml::from_str(yaml_text).map_err(Unparsable)?;
132    let base: &YamlMapping = if let Some(base) = base.as_mapping() { base } else {
133        return Err(BaseNotMap(get_type(&base)))
134    };
135
136    let (
137        categories,
138        authors,
139        hints,
140        files,
141    ) = {
142        let categories = get_map!(
143            base.categories, value_to_categories,
144            default,
145        ).map_err(AttribError::Categories);
146    
147        let authors = get_map!(
148            base.authors, as_str_list,
149            default,
150        ).map_err(AttribError::Authors);
151
152        let hints = get_map!(
153            base.hints, as_str_list,
154        ).map_err(AttribError::Hints);
155
156        let files = base.get("files")
157            .map(|value| file_list(value, base_path)).flop()
158            .map_err(Files);
159        
160        (categories, authors, hints, files)
161    };
162
163
164    let deploy = base
165        .get("deploy")
166        .map(parse_deploy)
167        .flop()
168        .map_err(Deploy);
169
170
171    let points = get_primitive!(base.value (as_u64) else PointsNotInt);
172
173    let flag = get_map!(
174        base.flag, |value| get_flag(value, base_path),
175        default,
176    ).map_err(AttribError::Flag);
177    
178    let name = get_primitive!(base.name (as_str => str::to_string) else NameNotString);
179    let description = get_primitive!(base.description (as_str => str::to_string) else DescNotString);
180    let visible = get_primitive!(base.visible (as_bool) else VisNotBool);
181
182    let (
183        authors,
184        categories,
185        hints,
186        files,
187        
188        deploy,
189
190        points,
191        flag,
192        
193        name,
194        description,
195
196        visible,
197    ) = collect_errors!(
198        authors,
199        categories,
200        hints,
201        files,
202        
203        deploy,
204
205        points,
206        flag,
207
208        name,
209        description,
210        
211        visible,
212    ).map_err(PartErrors)?;
213
214    let shape = YamlShape {
215        authors, categories, hints, files,
216        deploy,
217        points, flag,
218        name, description,
219        visible,
220    };
221    correctness.verify(&shape).map_err(Correctness)?;
222
223    Ok(shape)
224}
225
226#[doc(hidden)]
227pub mod __main {
228    use std::path::PathBuf;
229    use std::str::FromStr;
230    use std::sync::atomic::AtomicBool;
231
232    use crate::correctness::YamlCorrectness;
233    use crate::YamlShape;
234
235    pub fn main(yaml_correctness: &YamlCorrectness) {
236        let errors_encountered = AtomicBool::new(false);
237
238        macro_rules! set_err_if {
239            ($result:expr; $err_ctr:ident: CL ($err_print_stmt:expr); $($mapper:expr)?) => {
240                match ($result) {
241                    Ok(value) => Some($($mapper)?(value)),
242                    Err(err) => {
243                        ($err_print_stmt)(err);
244                        $err_ctr.store(true, core::sync::atomic::Ordering::SeqCst);
245                        None
246                    }
247                }
248            };
249            ($result:expr; $err_ctr:ident: $err_print_stmt:expr; $($mapper:expr)?) => {{
250                match ($result) {
251                    Ok(value) => Some($($mapper)?(value)),
252                    Err(_) => {
253                        $err_print_stmt;
254                        $err_ctr.store(true, core::sync::atomic::Ordering::SeqCst);
255                        None
256                    }
257                }
258            }};
259        }
260
261        std::env::args()
262            .skip(1)
263            .filter_map(|path| set_err_if!(
264                PathBuf::from_str(&path);
265                errors_encountered: println!("`{path}` is not a valid path!");
266            ))
267            .filter_map(|mut path| {
268                println!("{:-^40}", path.display());
269                set_err_if!(
270                    std::fs::read_to_string(&path);
271                    errors_encountered: println!("Failed to read `{}` to string. Check location, permissions, and encoding of the file.", path.display());
272                    |data| {
273                        path.pop();
274                        (data, path)
275                    }
276                )
277            })
278            .for_each(|(data, base_path)| {
279                set_err_if!(
280                    YamlShape::try_from_str(&data, &yaml_correctness.clone(), Some(&base_path));
281                    errors_encountered: CL (|err| eprintln!("{err}"));
282                    |yaml| println!("{yaml:#?}")   
283                );
284            });
285        if errors_encountered.load(core::sync::atomic::Ordering::SeqCst) {
286            std::process::exit(1);
287        }
288    }
289}
290
291
292trait Flop {
293    type Target;
294    fn flop(self) -> Self::Target;
295}
296impl<T, E> Flop for Option<Result<T, E>> {
297    type Target = Result<Option<T>, E>;
298    fn flop(self) -> Self::Target {
299        if let Some(res) = self {
300            res.map(Some)
301        } else { Ok(None) }
302    }
303}
304impl<T, E> Flop for Result<Option<T>, E> {
305    type Target = Option<Result<T, E>>;
306    fn flop(self) -> Self::Target {
307        match self {
308            Ok(Some(res)) => Some(Ok(res)),
309            Ok(None) => None,
310            Err(e) => Some(Err(e)),
311        }
312    }
313}
314