fastobo_validator/
isbn.rs

1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::error::Error;
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::fmt::Result as FmtResult;
7use std::str::FromStr;
8
9use fastobo::ast::*;
10use fastobo::semantics::Identified;
11use fastobo::visit::Visit;
12use isbn_crate::Isbn;
13
14use super::ValidationError;
15use super::Validator;
16
17#[derive(Debug)]
18pub struct InvalidIsbn(PrefixedIdent, isbn_crate::IsbnError);
19
20impl Display for InvalidIsbn {
21    fn fmt(&self, f: &mut Formatter) -> FmtResult {
22        write!(f, "invalid isbn `{}` ", self.0)?;
23        f.write_str(match self.1 {
24            isbn_crate::IsbnError::InvalidChecksum => "(invalid checksum)",
25            isbn_crate::IsbnError::InvalidLength => "(invalid length)",
26            isbn_crate::IsbnError::InvalidDigit => "(invalid digit)",
27            isbn_crate::IsbnError::InvalidGroup => "(invalid group)",
28            isbn_crate::IsbnError::InvalidConversion => "(invalid conversion)",
29            isbn_crate::IsbnError::UndefinedRange => "(undefined range)",
30        })
31    }
32}
33
34impl Error for InvalidIsbn {
35    fn description(&self) -> &str {
36        "invalid isbn"
37    }
38}
39
40#[derive(Default)]
41pub struct IsbnChecker<'a> {
42    current_entity: Option<&'a Ident>,
43    valid: HashSet<&'a PrefixedIdent>,
44    invalid: HashMap<&'a Ident, Vec<InvalidIsbn>>,
45}
46
47impl<'a> Visit<'a> for IsbnChecker<'a> {
48    fn visit_entity_frame(&mut self, entity: &'a EntityFrame) {
49        self.current_entity = Some(entity.as_id());
50        match entity {
51            EntityFrame::Term(t) => self.visit_term_frame(t),
52            EntityFrame::Typedef(t) => self.visit_typedef_frame(t),
53            EntityFrame::Instance(i) => self.visit_instance_frame(i),
54        }
55    }
56
57    fn visit_prefixed_ident(&mut self, id: &'a PrefixedIdent) {
58        if id.prefix() == "ISBN" {
59            if let Err(e) = Isbn::from_str(id.local()) {
60                self.invalid
61                    .entry(self.current_entity.unwrap())
62                    .or_default()
63                    .push(InvalidIsbn(id.clone(), e));
64            } else {
65                self.valid.insert(id);
66            }
67        }
68    }
69}
70
71impl Validator for IsbnChecker<'_> {
72    fn validate(doc: &OboDoc) -> Vec<ValidationError> {
73        let mut checker = Self::default();
74        checker.visit_doc(doc);
75
76        let mut errors = Vec::new();
77        for (entity, errs) in checker.invalid {
78            for err in errs {
79                errors.push(ValidationError {
80                    location: format!("frame {}", entity),
81                    cause: Box::new(err),
82                })
83            }
84        }
85
86        errors
87    }
88}