fastobo_validator/
obsoletion.rs

1use std::error::Error;
2use std::fmt::Display;
3use std::fmt::Formatter;
4use std::fmt::Result as FmtResult;
5
6use fastobo::ast::*;
7
8use fastobo::visit::Visit;
9
10use super::ValidationError;
11use super::Validator;
12
13#[derive(Debug)]
14pub struct ObsoletionError {
15    id: Ident,
16    replaced_by: usize,
17    consider: usize,
18}
19
20impl Display for ObsoletionError {
21    fn fmt(&self, f: &mut Formatter) -> FmtResult {
22        if self.replaced_by > 0 {
23            write!(
24                f,
25                "non-obsolete entity contains {} `replaced_by` clause(s)",
26                self.replaced_by
27            )
28        } else {
29            write!(
30                f,
31                "non-obsolete entity contains {} `consider` clause(s)",
32                self.consider
33            )
34        }
35    }
36}
37
38impl Error for ObsoletionError {
39    fn description(&self) -> &str {
40        "non-obsolete frame contains obsoletion clauses"
41    }
42}
43
44#[derive(Default)]
45pub struct ObsoletionChecker {
46    errors: Vec<ObsoletionError>,
47}
48
49macro_rules! impl_visit {
50    ($name:ident, $frame:ty, $clause:ident) => {
51        fn $name(&mut self, frame: &'a $frame) {
52            let mut obsolete = false;
53            let mut replaced_by: Vec<&'a Ident> = Vec::new();
54            let mut consider: Vec<&'a Ident> = Vec::new();
55
56            for clause in frame.clauses() {
57                match clause.as_inner() {
58                    $clause::IsObsolete(true) => obsolete = true,
59                    $clause::ReplacedBy(id) => replaced_by.push(id.as_ref().as_ref()),
60                    $clause::Consider(id) => consider.push(id.as_ref().as_ref()),
61                    _ => (),
62                }
63            }
64
65            if (replaced_by.len() > 0 || consider.len() > 1) && !obsolete {
66                self.errors.push(ObsoletionError {
67                    id: frame.id().as_inner().as_ref().as_ref().clone(),
68                    replaced_by: replaced_by.len(),
69                    consider: consider.len(),
70                })
71            }
72        }
73    };
74}
75
76impl<'a> Visit<'a> for ObsoletionChecker {
77    impl_visit!(visit_term_frame, TermFrame, TermClause);
78    impl_visit!(visit_typedef_frame, TypedefFrame, TypedefClause);
79    impl_visit!(visit_instance_frame, InstanceFrame, InstanceClause);
80}
81
82impl Validator for ObsoletionChecker {
83    fn validate(doc: &OboDoc) -> Vec<ValidationError> {
84        let mut checker = Self::default();
85        checker.visit_doc(doc);
86
87        let mut errors = Vec::new();
88        for err in checker.errors {
89            errors.push(ValidationError {
90                location: format!("frame {}", err.id),
91                cause: Box::new(err),
92            })
93        }
94
95        errors
96    }
97}