use crate::error::{MarcError, Result};
use crate::record::Field;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct IndicatorRules {
pub tag: String,
pub indicator1: IndicatorValidation,
pub indicator2: IndicatorValidation,
}
#[derive(Debug, Clone)]
pub enum IndicatorValidation {
Undefined,
Any,
Values(Vec<char>),
DigitRange {
min: u8,
max: u8,
},
Obsolete,
}
#[derive(Debug, Clone)]
pub struct IndicatorMeaning {
pub value: char,
pub meaning: String,
}
impl IndicatorValidation {
#[must_use]
pub fn is_valid(&self, c: char) -> bool {
match self {
IndicatorValidation::Undefined => c == '#' || c == ' ',
IndicatorValidation::Any | IndicatorValidation::Obsolete => true,
IndicatorValidation::Values(values) => values.contains(&c),
IndicatorValidation::DigitRange { min, max } => {
if let Some(digit) = c.to_digit(10) {
#[allow(clippy::cast_possible_truncation)]
let d = digit as u8;
d >= *min && d <= *max
} else {
false
}
},
}
}
}
type SemanticMap = HashMap<char, String>;
#[derive(Debug)]
pub struct IndicatorValidator {
rules: HashMap<String, IndicatorRules>,
semantics: HashMap<String, (SemanticMap, SemanticMap)>, }
impl IndicatorValidator {
#[must_use]
pub fn new() -> Self {
let rules = Self::build_standard_rules();
let semantics = Self::build_semantic_rules();
IndicatorValidator { rules, semantics }
}
#[allow(clippy::too_many_lines)]
fn build_standard_rules() -> HashMap<String, IndicatorRules> {
let mut rules = HashMap::new();
let undefined_undefined = IndicatorRules {
tag: String::new(),
indicator1: IndicatorValidation::Undefined,
indicator2: IndicatorValidation::Undefined,
};
for tag in &["010", "020", "024", "028", "030", "035", "037", "040"] {
let mut rule = undefined_undefined.clone();
rule.tag = (*tag).to_string();
rules.insert((*tag).to_string(), rule);
}
rules.insert(
"100".to_string(),
IndicatorRules {
tag: "100".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1', '3']),
indicator2: IndicatorValidation::Undefined,
},
);
rules.insert(
"110".to_string(),
IndicatorRules {
tag: "110".to_string(),
indicator1: IndicatorValidation::Values(vec!['1', '2']),
indicator2: IndicatorValidation::Undefined,
},
);
rules.insert(
"111".to_string(),
IndicatorRules {
tag: "111".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1', '2']),
indicator2: IndicatorValidation::Undefined,
},
);
rules.insert(
"130".to_string(),
IndicatorRules {
tag: "130".to_string(),
indicator1: IndicatorValidation::DigitRange { min: 0, max: 9 },
indicator2: IndicatorValidation::Undefined,
},
);
rules.insert(
"240".to_string(),
IndicatorRules {
tag: "240".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1']),
indicator2: IndicatorValidation::DigitRange { min: 0, max: 9 },
},
);
rules.insert(
"245".to_string(),
IndicatorRules {
tag: "245".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1']),
indicator2: IndicatorValidation::DigitRange { min: 0, max: 9 },
},
);
rules.insert(
"490".to_string(),
IndicatorRules {
tag: "490".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1']),
indicator2: IndicatorValidation::Undefined,
},
);
for tag in &[
"500", "501", "502", "504", "505", "506", "508", "511", "520", "521", "522", "524",
"525", "526", "530", "533", "535", "538", "541", "546", "547", "550", "552", "555",
"556", "561", "562", "563", "565", "567", "570", "580", "581", "583", "586", "588",
] {
let mut rule = undefined_undefined.clone();
rule.tag = (*tag).to_string();
rules.insert((*tag).to_string(), rule);
}
rules.insert(
"600".to_string(),
IndicatorRules {
tag: "600".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1', '3']),
indicator2: IndicatorValidation::Values(vec![
'0', '1', '2', '3', '4', '5', '6', '7',
]),
},
);
rules.insert(
"610".to_string(),
IndicatorRules {
tag: "610".to_string(),
indicator1: IndicatorValidation::Values(vec!['1', '2']),
indicator2: IndicatorValidation::DigitRange { min: 0, max: 7 },
},
);
rules.insert(
"611".to_string(),
IndicatorRules {
tag: "611".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1', '2']),
indicator2: IndicatorValidation::DigitRange { min: 0, max: 7 },
},
);
rules.insert(
"650".to_string(),
IndicatorRules {
tag: "650".to_string(),
indicator1: IndicatorValidation::Undefined,
indicator2: IndicatorValidation::DigitRange { min: 0, max: 7 },
},
);
rules.insert(
"651".to_string(),
IndicatorRules {
tag: "651".to_string(),
indicator1: IndicatorValidation::Undefined,
indicator2: IndicatorValidation::DigitRange { min: 0, max: 7 },
},
);
rules.insert(
"700".to_string(),
IndicatorRules {
tag: "700".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1', '3']),
indicator2: IndicatorValidation::Values(vec!['#', ' ', '2']),
},
);
rules.insert(
"710".to_string(),
IndicatorRules {
tag: "710".to_string(),
indicator1: IndicatorValidation::Values(vec!['1', '2']),
indicator2: IndicatorValidation::Values(vec!['#', ' ', '2']),
},
);
rules.insert(
"711".to_string(),
IndicatorRules {
tag: "711".to_string(),
indicator1: IndicatorValidation::Values(vec!['0', '1', '2']),
indicator2: IndicatorValidation::Values(vec!['#', ' ', '2']),
},
);
rules.insert(
"740".to_string(),
IndicatorRules {
tag: "740".to_string(),
indicator1: IndicatorValidation::DigitRange { min: 0, max: 9 },
indicator2: IndicatorValidation::Values(vec!['#', ' ', '2']),
},
);
for tag in &["800", "810", "811", "830", "840", "856"] {
let mut rule = undefined_undefined.clone();
rule.tag = (*tag).to_string();
rules.insert((*tag).to_string(), rule);
}
rules
}
pub fn validate_field(&self, field: &Field) -> Result<()> {
self.validate_indicators(&field.tag, field.indicator1, field.indicator2)
}
pub fn validate_indicators(&self, tag: &str, indicator1: char, indicator2: char) -> Result<()> {
if let Some(rules) = self.rules.get(tag) {
if !rules.indicator1.is_valid(indicator1) {
return Err(MarcError::InvalidField(format!(
"Invalid indicator1 '{}' for field {}: expected {:?}",
indicator1, tag, rules.indicator1
)));
}
if !rules.indicator2.is_valid(indicator2) {
return Err(MarcError::InvalidField(format!(
"Invalid indicator2 '{}' for field {}: expected {:?}",
indicator2, tag, rules.indicator2
)));
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn build_semantic_rules() -> HashMap<String, (SemanticMap, SemanticMap)> {
let mut semantics = HashMap::new();
let mut ind1 = HashMap::new();
ind1.insert('0', "Forename".to_string());
ind1.insert('1', "Surname".to_string());
ind1.insert('3', "Family name".to_string());
let ind2 = HashMap::new(); semantics.insert("100".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('1', "Jurisdiction".to_string());
ind1.insert('2', "Name in direct order".to_string());
let ind2 = HashMap::new(); semantics.insert("110".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('0', "Inverted name".to_string());
ind1.insert('1', "Jurisdiction".to_string());
ind1.insert('2', "Name in direct order".to_string());
let ind2 = HashMap::new(); semantics.insert("111".to_string(), (ind1, ind2));
let ind2 = HashMap::new(); semantics.insert("130".to_string(), (HashMap::new(), ind2));
let mut ind1 = HashMap::new();
ind1.insert('0', "Not printed".to_string());
ind1.insert('1', "Printed".to_string());
semantics.insert("240".to_string(), (ind1, HashMap::new()));
let mut ind1 = HashMap::new();
ind1.insert('0', "No title added entry".to_string());
ind1.insert('1', "Title added entry".to_string());
semantics.insert("245".to_string(), (ind1, HashMap::new()));
let mut ind1 = HashMap::new();
ind1.insert('0', "Not traced".to_string());
ind1.insert('1', "Traced".to_string());
let ind2 = HashMap::new(); semantics.insert("490".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('0', "Forename".to_string());
ind1.insert('1', "Surname".to_string());
ind1.insert('3', "Family name".to_string());
let mut ind2 = HashMap::new();
ind2.insert('0', "LCSH".to_string());
ind2.insert('1', "LCSH (conflict)".to_string());
ind2.insert('2', "MeSH".to_string());
ind2.insert('3', "NAL".to_string());
ind2.insert('4', "Source not specified".to_string());
ind2.insert('5', "Canadian subjects".to_string());
ind2.insert('6', "RVM (répertoire de vedettes-matière)".to_string());
ind2.insert('7', "Source in $2".to_string());
semantics.insert("600".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('1', "Jurisdiction".to_string());
ind1.insert('2', "Name in direct order".to_string());
let mut ind2 = HashMap::new();
ind2.insert('0', "LCSH".to_string());
ind2.insert('1', "LCSH (conflict)".to_string());
ind2.insert('2', "MeSH".to_string());
ind2.insert('3', "NAL".to_string());
ind2.insert('4', "Source not specified".to_string());
ind2.insert('5', "Canadian subjects".to_string());
ind2.insert('6', "RVM".to_string());
ind2.insert('7', "Source in $2".to_string());
semantics.insert("610".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('0', "Inverted name".to_string());
ind1.insert('1', "Jurisdiction".to_string());
ind1.insert('2', "Name in direct order".to_string());
let mut ind2 = HashMap::new();
ind2.insert('0', "LCSH".to_string());
ind2.insert('1', "LCSH (conflict)".to_string());
ind2.insert('2', "MeSH".to_string());
ind2.insert('3', "NAL".to_string());
ind2.insert('4', "Source not specified".to_string());
ind2.insert('5', "Canadian subjects".to_string());
ind2.insert('6', "RVM".to_string());
ind2.insert('7', "Source in $2".to_string());
semantics.insert("611".to_string(), (ind1, ind2));
let ind1 = HashMap::new(); let mut ind2 = HashMap::new();
ind2.insert('0', "LCSH".to_string());
ind2.insert('1', "LCSH (conflict)".to_string());
ind2.insert('2', "MeSH".to_string());
ind2.insert('3', "NAL".to_string());
ind2.insert('4', "Source not specified".to_string());
ind2.insert('5', "Canadian subjects".to_string());
ind2.insert('6', "RVM".to_string());
ind2.insert('7', "Source in $2".to_string());
semantics.insert("650".to_string(), (ind1, ind2));
let ind1 = HashMap::new(); let mut ind2 = HashMap::new();
ind2.insert('0', "LCSH".to_string());
ind2.insert('1', "LCSH (conflict)".to_string());
ind2.insert('2', "MeSH".to_string());
ind2.insert('3', "NAL".to_string());
ind2.insert('4', "Source not specified".to_string());
ind2.insert('5', "Canadian subjects".to_string());
ind2.insert('6', "RVM".to_string());
ind2.insert('7', "Source in $2".to_string());
semantics.insert("651".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('0', "Forename".to_string());
ind1.insert('1', "Surname".to_string());
ind1.insert('3', "Family name".to_string());
let mut ind2 = HashMap::new();
ind2.insert('#', "No additional information".to_string());
ind2.insert(' ', "No additional information".to_string());
ind2.insert('2', "Analytical entry".to_string());
semantics.insert("700".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('1', "Jurisdiction".to_string());
ind1.insert('2', "Name in direct order".to_string());
let mut ind2 = HashMap::new();
ind2.insert('#', "No additional information".to_string());
ind2.insert(' ', "No additional information".to_string());
ind2.insert('2', "Analytical entry".to_string());
semantics.insert("710".to_string(), (ind1, ind2));
let mut ind1 = HashMap::new();
ind1.insert('0', "Inverted".to_string());
ind1.insert('1', "Jurisdiction".to_string());
ind1.insert('2', "Name in direct order".to_string());
let mut ind2 = HashMap::new();
ind2.insert('#', "No additional information".to_string());
ind2.insert(' ', "No additional information".to_string());
ind2.insert('2', "Analytical entry".to_string());
semantics.insert("711".to_string(), (ind1, ind2));
let mut ind2 = HashMap::new();
ind2.insert('#', "No additional information".to_string());
ind2.insert(' ', "No additional information".to_string());
ind2.insert('2', "Analytical entry".to_string());
semantics.insert("740".to_string(), (HashMap::new(), ind2));
semantics
}
#[must_use]
pub fn get_indicator_meaning(
&self,
tag: &str,
indicator_num: u8,
value: char,
) -> Option<String> {
if indicator_num != 1 && indicator_num != 2 {
return None;
}
self.semantics.get(tag).and_then(|(ind1, ind2)| {
if indicator_num == 1 {
ind1.get(&value).cloned()
} else {
ind2.get(&value).cloned()
}
})
}
#[must_use]
pub fn get_indicator_meanings(&self, tag: &str, indicator_num: u8) -> Vec<IndicatorMeaning> {
if indicator_num != 1 && indicator_num != 2 {
return Vec::new();
}
self.semantics
.get(tag)
.and_then(|(ind1, ind2)| {
let map = if indicator_num == 1 { ind1 } else { ind2 };
if map.is_empty() {
None
} else {
Some(
map.iter()
.map(|(&value, meaning)| IndicatorMeaning {
value,
meaning: meaning.clone(),
})
.collect(),
)
}
})
.unwrap_or_default()
}
#[must_use]
pub fn get_rules(&self, tag: &str) -> Option<&IndicatorRules> {
self.rules.get(tag)
}
}
impl Default for IndicatorValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_undefined_indicator_validation() {
let validation = IndicatorValidation::Undefined;
assert!(validation.is_valid('#'));
assert!(validation.is_valid(' '));
assert!(!validation.is_valid('0'));
assert!(!validation.is_valid('1'));
}
#[test]
fn test_values_indicator_validation() {
let validation = IndicatorValidation::Values(vec!['0', '1', '3']);
assert!(validation.is_valid('0'));
assert!(validation.is_valid('1'));
assert!(validation.is_valid('3'));
assert!(!validation.is_valid('2'));
assert!(!validation.is_valid('#'));
}
#[test]
fn test_digit_range_validation() {
let validation = IndicatorValidation::DigitRange { min: 0, max: 9 };
assert!(validation.is_valid('0'));
assert!(validation.is_valid('5'));
assert!(validation.is_valid('9'));
assert!(!validation.is_valid('a'));
assert!(!validation.is_valid('#'));
}
#[test]
fn test_field_100_validation() {
let validator = IndicatorValidator::new();
assert!(validator.validate_indicators("100", '1', '#').is_ok());
assert!(validator.validate_indicators("100", '0', ' ').is_ok());
assert!(validator.validate_indicators("100", '3', '#').is_ok());
assert!(validator.validate_indicators("100", '2', '#').is_err()); assert!(validator.validate_indicators("100", '1', '0').is_err()); }
#[test]
fn test_field_245_validation() {
let validator = IndicatorValidator::new();
assert!(validator.validate_indicators("245", '1', '0').is_ok());
assert!(validator.validate_indicators("245", '0', '4').is_ok());
assert!(validator.validate_indicators("245", '1', '9').is_ok());
assert!(validator.validate_indicators("245", '2', '0').is_err()); assert!(validator.validate_indicators("245", '1', 'a').is_err()); }
#[test]
fn test_field_650_validation() {
let validator = IndicatorValidator::new();
assert!(validator.validate_indicators("650", '#', '0').is_ok());
assert!(validator.validate_indicators("650", ' ', '7').is_ok());
assert!(validator.validate_indicators("650", '#', '2').is_ok()); assert!(validator.validate_indicators("650", '0', '0').is_err()); assert!(validator.validate_indicators("650", '#', '8').is_err()); }
#[test]
fn test_field_700_validation() {
let validator = IndicatorValidator::new();
assert!(validator.validate_indicators("700", '1', '#').is_ok());
assert!(validator.validate_indicators("700", '1', ' ').is_ok());
assert!(validator.validate_indicators("700", '3', '2').is_ok());
assert!(validator.validate_indicators("700", '1', '0').is_err()); }
#[test]
fn test_indicator_meaning_100() {
let validator = IndicatorValidator::new();
assert_eq!(
validator.get_indicator_meaning("100", 1, '0'),
Some("Forename".to_string())
);
assert_eq!(
validator.get_indicator_meaning("100", 1, '1'),
Some("Surname".to_string())
);
assert_eq!(
validator.get_indicator_meaning("100", 1, '3'),
Some("Family name".to_string())
);
assert_eq!(validator.get_indicator_meaning("100", 2, '0'), None); }
#[test]
fn test_indicator_meaning_245() {
let validator = IndicatorValidator::new();
assert_eq!(
validator.get_indicator_meaning("245", 1, '0'),
Some("No title added entry".to_string())
);
assert_eq!(
validator.get_indicator_meaning("245", 1, '1'),
Some("Title added entry".to_string())
);
assert_eq!(validator.get_indicator_meaning("245", 2, '0'), None);
}
#[test]
fn test_indicator_meaning_650() {
let validator = IndicatorValidator::new();
assert_eq!(
validator.get_indicator_meaning("650", 2, '0'),
Some("LCSH".to_string())
);
assert_eq!(
validator.get_indicator_meaning("650", 2, '2'),
Some("MeSH".to_string())
);
assert_eq!(
validator.get_indicator_meaning("650", 2, '7'),
Some("Source in $2".to_string())
);
assert_eq!(validator.get_indicator_meaning("650", 1, '0'), None); }
#[test]
fn test_get_indicator_meanings_100() {
let validator = IndicatorValidator::new();
let meanings = validator.get_indicator_meanings("100", 1);
assert!(!meanings.is_empty());
assert!(meanings
.iter()
.any(|m| m.value == '0' && m.meaning == "Forename"));
assert!(meanings
.iter()
.any(|m| m.value == '1' && m.meaning == "Surname"));
}
#[test]
fn test_get_indicator_meanings_empty() {
let validator = IndicatorValidator::new();
let meanings = validator.get_indicator_meanings("100", 2);
assert!(meanings.is_empty());
}
#[test]
fn test_get_indicator_meanings_650() {
let validator = IndicatorValidator::new();
let meanings = validator.get_indicator_meanings("650", 2);
assert!(!meanings.is_empty());
assert_eq!(meanings.len(), 8); assert!(meanings
.iter()
.any(|m| m.value == '0' && m.meaning == "LCSH"));
assert!(meanings
.iter()
.any(|m| m.value == '7' && m.meaning == "Source in $2"));
}
}