Commit 7dd00f76 authored by Wohlgemuth, Jason's avatar Wohlgemuth, Jason
Browse files

feat: Remove more TODO comments; Add support for repeatable fields in BagInfo

parent f561bb2c
Loading
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -684,7 +684,6 @@ where
        | Err(e) => Err(format!("Failed to parse ORCiD search response: {e}")),
    }
}
// TODO: Support OR statements for field values (e.g., affiliation-org-name:("University of Plymouth" OR "Plymouth University"))
/// Construct a query string for an endpoint API query from a list of field-value pairs, a list of fields, and a list of fields with boosted relevance.
///
/// The query string is constructed by joining the following parts with "&":
+21 −16
Original line number Diff line number Diff line
@@ -81,26 +81,26 @@ pub struct Bag {
#[builder(start_fn = init)]
pub struct BagInfo {
    /// Organization transferring the content (Source-Organization)
    pub organization: Option<String>,
    pub organization: Option<Vec<String>>,
    /// Mailing address of the source organization (Organization-Address)
    pub organization_address: Option<String>,
    pub organization_address: Option<Vec<String>>,
    /// Person responsible for the content transfer (Contact-Name)
    pub contact_name: Option<String>,
    pub contact_name: Option<Vec<String>>,
    /// International format telephone number of responsible person (Contact-Phone)
    pub contact_phone: Option<String>,
    pub contact_phone: Option<Vec<String>>,
    /// Fully qualified email address of responsible person (Contact-Email)
    pub contact_email: Option<String>,
    pub contact_email: Option<Vec<String>>,
    /// Brief explanation of contents and provenance (External-Description)
    pub description: Option<String>,
    pub description: Option<Vec<String>>,
    /// Date (YYYY-MM-DD) that content was prepared for transfer (Bagging-Date)
    pub date: Option<String>,
    /// Sender-supplied identifier for the bag (External-Identifier)
    pub identifier: Option<String>,
    pub identifier: Option<Vec<String>>,
    /// Size or approximate size of the bag with abbreviation (Bag-Size)
    pub size: Option<String>,
    /// Bag count as (N, T) tuple where N is ordinal position and T is total (Bag-Count)
    /// Bag count values as (N, T) tuples where N is ordinal position and T is total
    /// T is None if unknown ("?")
    pub count: Option<(u32, Option<u32>)>,
    pub count: Option<Vec<(u32, Option<u32>)>>,
}
impl Bag {
    /// Verify the completeness and correctness of a bag
@@ -280,25 +280,30 @@ impl BagInfo {
            size,
            count,
        } = self;
        let base = [
        let repeatable = [
            ("Source-Organization", organization),
            ("Organization-Address", organization_address),
            ("Contact-Name", contact_name),
            ("Contact-Phone", contact_phone),
            ("Contact-Email", contact_email),
            ("External-Description", description),
            ("Bagging-Date", date),
            ("External-Identifier", identifier),
            ("Bag-Size", size),
        ]
        .into_iter()
        .filter_map(|(key, value)| value.as_ref().map(|v| (key.to_string(), v.clone())));
        // Option as iterator: Some(item) -> one element, None -> empty.
        let count = count.iter().map(|(index, total)| {
        .flat_map(|(key, values)| {
            values
                .iter()
                .flat_map(|items| items.iter())
                .map(move |value| (key.to_string(), value.clone()))
        });
        let count = count.iter().flat_map(|items| items.iter()).map(|(index, total)| {
            let total = total.map_or_else(|| "?".to_string(), |value| value.to_string());
            ("Bag-Count".to_string(), format!("{index} of {total}"))
        });
        base.chain(count).collect()
        let single = [("Bagging-Date", date.clone()), ("Bag-Size", size.clone())]
            .into_iter()
            .filter_map(|(key, value)| value.map(|v| (key.to_string(), v)));
        repeatable.chain(count).chain(single).collect()
    }
}
impl Default for BagInfo {
+51 −3
Original line number Diff line number Diff line
@@ -7,7 +7,9 @@ use crate::io::{
    filter_ignored, image_paths, InputOutput,
};
use crate::{Location, Repository, Scheme};
use std::fs::{create_dir_all, read_to_string, remove_dir_all};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

const ENDPOINTS: &str = r#"{
    "endpoints": [
@@ -75,6 +77,7 @@ const ENDPOINTS: &str = r#"{
        }
    ]
}"#;
const TEST_BAGGING_DATE: &str = "2026-02-22";

fn fixtures_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join("tests/fixtures")
@@ -219,7 +222,6 @@ fn test_runners() {
    let config = ApplicationConfiguration::read_json(path).unwrap();
    insta::assert_snapshot!("acorn__io__config__tests__runners", format!("{:#?}", config.runners));
}

#[test]
fn test_archive() {
    let path = fixtures_dir().join("data");
@@ -229,8 +231,8 @@ fn test_archive() {
#[test]
fn test_bagit() {
    let info = BagInfo::init()
        .organization("Oak Ridge National Laboratory".to_string())
        .contact_name("Jason Wohlgemuth".to_string())
        .organization(vec!["Oak Ridge National Laboratory".to_string()])
        .contact_name(vec!["Jason Wohlgemuth".to_string()])
        .build();
    let bag = Bag::init()
        .base_directory(fixtures_dir().join("data").display().to_string())
@@ -247,6 +249,52 @@ fn test_bagit() {
    }
}
#[test]
fn test_baginfo_save_repeatable_fields() {
    let stamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
    let dir = std::env::temp_dir().join(format!("acorn-baginfo-{stamp}"));
    create_dir_all(dir.clone()).unwrap();
    let info = BagInfo::init()
        .organization(vec!["Org One".to_string(), "Org Two".to_string()])
        .contact_name(vec!["Alice".to_string(), "Bob".to_string()])
        .count(vec![(1, Some(3)), (2, None)])
        .date(TEST_BAGGING_DATE.to_string())
        .size("1 MB".to_string())
        .build();
    let path = info.save(dir.clone()).unwrap();
    assert_eq!(path, dir);
    let content = read_to_string(dir.join("bag-info.txt")).unwrap();
    let lines = content.lines().collect::<Vec<_>>();
    let expected_date = format!("Bagging-Date: {TEST_BAGGING_DATE}");
    assert_eq!(
        lines,
        vec![
            "Source-Organization: Org One",
            "Source-Organization: Org Two",
            "Contact-Name: Alice",
            "Contact-Name: Bob",
            "Bag-Count: 1 of 3",
            "Bag-Count: 2 of ?",
            expected_date.as_str(),
            "Bag-Size: 1 MB",
        ]
    );
    remove_dir_all(dir).unwrap();
}
#[test]
fn test_baginfo_save_single_value_fields_only() {
    let stamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
    let dir = std::env::temp_dir().join(format!("acorn-baginfo-single-{stamp}"));
    create_dir_all(dir.clone()).unwrap();
    let info = BagInfo::init().date(TEST_BAGGING_DATE.to_string()).size("42 KB".to_string()).build();
    let path = info.save(dir.clone()).unwrap();
    assert_eq!(path, dir);
    let content = read_to_string(dir.join("bag-info.txt")).unwrap();
    let lines = content.lines().collect::<Vec<_>>();
    let expected_date = format!("Bagging-Date: {TEST_BAGGING_DATE}");
    assert_eq!(lines, vec![expected_date.as_str(), "Bag-Size: 42 KB"]);
    remove_dir_all(dir).unwrap();
}
#[test]
#[ignore = "Verification is broke on macbook for some reason"]
fn test_bagit_verify() {
    let path = fixtures_dir().join("bag");
+1 −1
Original line number Diff line number Diff line
@@ -162,10 +162,10 @@ pub fn interpolate_values(path: PathBuf, data: ResearchActivity) {
                String(&'static str, String),
                Bullets(&'static str, Vec<String>),
            }
            // TODO: Use CiteAs API to get in Chicago format
            let replacements = vec![
                Replacement::String("caption", caption),
                Replacement::String("challenge", challenge),
                // TODO: Use CiteAs API to get in Chicago format
                Replacement::String("citation", doi.unwrap_or_else(|| vec!["".to_string()])[0].clone()),
                Replacement::String("email", email),
                Replacement::String("first", first),
+1 −1
Original line number Diff line number Diff line
@@ -708,7 +708,7 @@ impl ContactPoint {
    #[cfg(feature = "std")]
    pub fn format_with(self, _context: Option<PathBuf>) -> Self {
        // TODO: Resolve ORCiD identifier from first, last, and email
        self
        self.format()
    }
}
impl Default for ContactPoint {
Loading