Initial commit
This commit is contained in:
104
brittle-core/src/bibtex/validation.rs
Normal file
104
brittle-core/src/bibtex/validation.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::error::BibtexError;
|
||||
use crate::model::{EntryType, Reference};
|
||||
|
||||
/// Returns the required fields for a given BibTeX entry type.
|
||||
fn required_fields(entry_type: &EntryType) -> &'static [&'static str] {
|
||||
match entry_type {
|
||||
EntryType::Article => &["author", "title", "journal", "year"],
|
||||
EntryType::Book => &["title", "publisher", "year"],
|
||||
EntryType::Booklet => &["title"],
|
||||
EntryType::InBook => &["title", "publisher", "year", "chapter"],
|
||||
EntryType::InCollection => &["author", "title", "booktitle", "publisher", "year"],
|
||||
EntryType::InProceedings => &["author", "title", "booktitle", "year"],
|
||||
EntryType::Manual => &["title"],
|
||||
EntryType::MastersThesis => &["author", "title", "school", "year"],
|
||||
EntryType::Misc => &[],
|
||||
EntryType::PhdThesis => &["author", "title", "school", "year"],
|
||||
EntryType::Proceedings => &["title", "year"],
|
||||
EntryType::TechReport => &["author", "title", "institution", "year"],
|
||||
EntryType::Unpublished => &["author", "title", "note"],
|
||||
EntryType::Online => &["title", "url"],
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that a reference has all required fields for BibTeX export.
|
||||
/// Returns an error describing the first missing required field found.
|
||||
pub fn validate_for_export(reference: &Reference) -> Result<(), BibtexError> {
|
||||
let required = required_fields(&reference.entry_type);
|
||||
|
||||
for &field in required {
|
||||
let present = match field {
|
||||
"author" => !reference.authors.is_empty(),
|
||||
"editor" => !reference.editors.is_empty(),
|
||||
_ => reference.fields.contains_key(field),
|
||||
};
|
||||
if !present {
|
||||
return Err(BibtexError::MissingRequiredField {
|
||||
cite_key: reference.cite_key.clone(),
|
||||
entry_type: reference.entry_type.bibtex_name().to_owned(),
|
||||
field: field.to_owned(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::model::{EntryType, Person, Reference};
|
||||
|
||||
fn make_article() -> Reference {
|
||||
let mut r = Reference::new("doe2024", EntryType::Article);
|
||||
r.authors.push(Person::new("Doe"));
|
||||
r.fields.insert("title".into(), "A Paper".into());
|
||||
r.fields.insert("journal".into(), "Nature".into());
|
||||
r.fields.insert("year".into(), "2024".into());
|
||||
r
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_article_passes() {
|
||||
let r = make_article();
|
||||
assert!(validate_for_export(&r).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn article_missing_author_fails() {
|
||||
let mut r = make_article();
|
||||
r.authors.clear();
|
||||
let err = validate_for_export(&r).unwrap_err();
|
||||
assert!(
|
||||
matches!(err, BibtexError::MissingRequiredField { field, .. } if field == "author")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn article_missing_journal_fails() {
|
||||
let mut r = make_article();
|
||||
r.fields.remove("journal");
|
||||
let err = validate_for_export(&r).unwrap_err();
|
||||
assert!(
|
||||
matches!(err, BibtexError::MissingRequiredField { field, .. } if field == "journal")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn misc_has_no_required_fields() {
|
||||
let r = Reference::new("anon", EntryType::Misc);
|
||||
assert!(validate_for_export(&r).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn phd_thesis_requires_school() {
|
||||
let mut r = Reference::new("smith2020", EntryType::PhdThesis);
|
||||
r.authors.push(Person::new("Smith"));
|
||||
r.fields.insert("title".into(), "A Thesis".into());
|
||||
r.fields.insert("year".into(), "2020".into());
|
||||
let err = validate_for_export(&r).unwrap_err();
|
||||
assert!(
|
||||
matches!(err, BibtexError::MissingRequiredField { field, .. } if field == "school")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user