collective_score_client/
check.rs

1use std::{
2    borrow::Cow,
3    fmt,
4    io::{stdout, Write},
5};
6use typed_builder::TypedBuilder;
7use yansi::Paint;
8
9use crate::validator::Validator;
10
11pub type Message = Cow<'static, str>;
12
13#[derive(TypedBuilder)]
14pub struct Check<T>
15where
16    T: Validator,
17{
18    #[builder(setter(into))]
19    description: Message,
20    validator: T,
21    #[builder(default, setter(strip_option, into))]
22    hint: Option<Message>,
23}
24
25impl<T> Check<T>
26where
27    T: Validator + 'static,
28{
29    pub fn into_box(self) -> Box<dyn RunnableCheck> {
30        Box::new(self)
31    }
32}
33
34pub struct CheckErr;
35
36impl fmt::Debug for CheckErr {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        writeln!(f, "Check failed!")
39    }
40}
41
42pub trait RunnableCheck {
43    fn run(&self) -> Result<(), CheckErr>;
44}
45
46impl<T> RunnableCheck for Check<T>
47where
48    T: Validator,
49{
50    fn run(&self) -> Result<(), CheckErr> {
51        let mut stdout = stdout().lock();
52
53        write!(stdout, "{}… ", self.description.bold().blue().underline()).unwrap();
54        stdout.flush().unwrap();
55
56        if let Err(e) = self.validator.validate() {
57            writeln!(stdout, "{}", "Failed".bold().red()).unwrap();
58
59            writeln!(stdout, "\n{e:?}").unwrap();
60
61            if let Some(hint) = &self.hint {
62                let separator = "–––––––".bold().yellow();
63
64                writeln!(
65                    stdout,
66                    "\n{} 💡\n{separator}\n{hint}\n{separator}",
67                    "Hint".bold().yellow()
68                )
69                .unwrap();
70            }
71
72            return Err(CheckErr);
73        }
74
75        writeln!(stdout, "{}\n", "OK".bold().green()).unwrap();
76
77        stdout.flush().unwrap();
78
79        Ok(())
80    }
81}
82
83impl<const N: usize> RunnableCheck for [Box<dyn RunnableCheck>; N] {
84    fn run(&self) -> Result<(), CheckErr> {
85        for check in self {
86            if check.run().is_err() {
87                return Err(CheckErr);
88            }
89        }
90
91        Ok(())
92    }
93}
94
95pub type Task = (&'static str, Box<dyn RunnableCheck>);
96
97pub trait IntoTask: RunnableCheck + Sized + 'static {
98    fn into_task(self, task_name: &'static str) -> Task {
99        (task_name, Box::new(self))
100    }
101}
102
103impl<T> IntoTask for T where T: RunnableCheck + 'static {}