exam/
lib.rs

1//! Exam is a library and a cargo plugin to ensure your source code is at a good state by applying a series of inspections.
2//!
3//! We have the following inspections available, with more to come:
4//! - rustfmt
5//! - clippy
6//! - `cargo test`
7
8use std::{
9    any::{type_name, TypeId},
10    collections::HashMap,
11    error::Error,
12};
13
14use downcast_rs::{impl_downcast, Downcast};
15use exams::*;
16
17pub trait Exam: Downcast {
18    fn name(&self) -> &str {
19        type_name::<Self>()
20    }
21
22    fn apply(&mut self) -> Result<(), ExamFailure>;
23}
24
25impl_downcast!(Exam);
26
27pub struct ExamFailure {
28    pub error: Box<dyn Error + 'static>,
29    pub report: Option<String>,
30}
31
32pub struct Examiner {
33    map: HashMap<TypeId, Box<dyn Exam + 'static>>,
34}
35
36impl std::fmt::Debug for Examiner {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("Examiner")
39            .field(
40                "exams",
41                &self
42                    .map
43                    .iter()
44                    .map(|(k, v)| (k, v.name()))
45                    .collect::<Vec<_>>(),
46            )
47            .finish()
48    }
49}
50
51impl Examiner {
52    pub fn new() -> Self {
53        let mut this = Self::empty();
54
55        this.add(RustfmtExam).add(TestsExam).add(ClippyExam);
56
57        this
58    }
59
60    pub fn empty() -> Self {
61        Self {
62            map: HashMap::new(),
63        }
64    }
65
66    pub fn add<T: Exam + 'static>(&mut self, exam: T) -> &mut Self {
67        let exam: Box<dyn Exam> = Box::new(exam);
68        self.map.insert(TypeId::of::<T>(), exam);
69        self
70    }
71
72    pub fn delete<T: Exam + 'static>(&mut self) -> &mut Self {
73        self.remove::<T>();
74        self
75    }
76
77    pub fn remove<T: Exam + 'static>(&mut self) -> Option<T> {
78        self.map.remove(&TypeId::of::<T>()).map(|exam| {
79            *exam
80                .downcast::<T>()
81                .map_err(|_| panic!("This downcast shouldn't fail"))
82                .unwrap()
83        })
84    }
85
86    pub fn apply(&mut self) -> Result<(), Vec<ExamFailure>> {
87        let mut failed_exams = vec![];
88
89        let mut run = |exam| {
90            apply_collecting_failures(&mut failed_exams, exam);
91        };
92
93        for exam in self.map.values_mut() {
94            println!("Applying {}...", exam.name());
95            run(exam);
96        }
97
98        if failed_exams.is_empty() {
99            Ok(())
100        } else {
101            Err(failed_exams)
102        }
103    }
104}
105
106impl Default for Examiner {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112fn apply_collecting_failures(failed_exams: &mut Vec<ExamFailure>, exam: &mut Box<dyn Exam>) {
113    if let Err(e) = exam.apply() {
114        failed_exams.push(e);
115    }
116}
117
118#[derive(Debug, thiserror::Error)]
119pub enum ExamError {
120    #[error(transparent)]
121    RustFmt(#[from] exams::RustFmtError),
122}
123
124pub mod exams;