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") ); } }