1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::str::FromStr;

use fastobo::ast::*;
use fastobo::semantics::Identified;
use fastobo::visit::Visit;
use isbn_crate::Isbn;

use super::ValidationError;
use super::Validator;

#[derive(Debug, Error)]
pub struct InvalidIsbn(PrefixedIdent, isbn_crate::IsbnError);

impl Display for InvalidIsbn {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        write!(f, "invalid isbn `{}` ", self.0)?;
        f.write_str(match self.1 {
            isbn_crate::IsbnError::InvalidChecksum => "(invalid checksum)",
            isbn_crate::IsbnError::InvalidLength => "(invalid length)",
            isbn_crate::IsbnError::InvalidDigit => "(invalid digit)",
            isbn_crate::IsbnError::InvalidGroup => "(invalid group)",
            isbn_crate::IsbnError::InvalidConversion => "(invalid conversion)",
            isbn_crate::IsbnError::UndefinedRange => "(undefined range)",
        })
    }
}

#[derive(Default)]
pub struct IsbnChecker<'a> {
    current_entity: Option<&'a Ident>,
    valid: HashSet<&'a PrefixedIdent>,
    invalid: HashMap<&'a Ident, Vec<InvalidIsbn>>,
}

impl<'a> Visit<'a> for IsbnChecker<'a> {
    fn visit_entity_frame(&mut self, entity: &'a EntityFrame) {
        self.current_entity = Some(entity.as_id());
        match entity {
            EntityFrame::Term(t) => self.visit_term_frame(t),
            EntityFrame::Typedef(t) => self.visit_typedef_frame(t),
            EntityFrame::Instance(i) => self.visit_instance_frame(i),
        }
    }

    fn visit_prefixed_ident(&mut self, id: &'a PrefixedIdent) {
        if id.prefix().as_str() == "ISBN" {
            if let Err(e) = Isbn::from_str(id.local().as_str()) {
                self.invalid
                    .entry(self.current_entity.unwrap())
                    .or_default()
                    .push(InvalidIsbn(id.clone(), e));
            } else {
                self.valid.insert(id);
            }
        }
    }
}

impl Validator for IsbnChecker<'_> {
    fn validate(doc: &OboDoc) -> Vec<ValidationError> {
        let mut checker = Self::default();
        checker.visit_doc(&doc);

        let mut errors = Vec::new();
        for (entity, errs) in checker.invalid {
            for err in errs {
                errors.push(ValidationError {
                    location: format!("frame {}", entity),
                    cause: Box::new(err),
                })
            }
        }

        errors
    }
}