fastobo_validator/
isbn.rs1use 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}