105 lines
3.7 KiB
Rust
105 lines
3.7 KiB
Rust
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")
|
|
);
|
|
}
|
|
}
|