rsxiv 0.4.3

Tools for working with arXiv and the arXiv API
Documentation
use super::*;

#[test]
fn test_set_version() {
    for (a, b, v) in [
        ("hep-th/0501001", "hep-th/0501001v256", 256),
        ("hep-th/0501001", "hep-th/0501001v65535", 65535),
        ("hep-th/0501001", "hep-th/0501001v1", 1),
        ("hep-th/0501001", "hep-th/0501001", 0),
        ("2212.99999", "2212.99999v65535", 65535),
        ("2212.99999", "2212.99999v1", 1),
        ("2212.99999", "2212.99999", 0),
    ] {
        let a_id = ArticleId::parse(a).unwrap();
        let b_id = ArticleId::parse(b).unwrap();

        let bp = b_id.clear_version();
        assert_eq!(a_id, bp);
        assert_eq!(a, a_id.to_string());
        assert_eq!(a, bp.to_string());

        assert_eq!(b_id, a_id.set_version(NonZero::new(v)));
    }
}

#[test]
fn test_sort_order() {
    fn assert_strictly_increasing(lst: &[&str]) {
        let order_1 = lst.iter();
        let order_2 = lst.iter().skip(1);

        for (smaller, larger) in order_1.zip(order_2) {
            assert!(ArticleId::parse(smaller).unwrap() < ArticleId::parse(larger).unwrap());
        }
    }

    assert_strictly_increasing(&[
        "hep-th/0501001",
        "hep-th/0501002",
        "nlin/0501001",
        "nlin/0501002",
        "hep-th/0502001",
        "astro-ph/0703999v65535",
        "math/0703999v1",
        "math/0703999v65535",
        "0704.0001",
        "0704.0001v65535",
        "0704.0002",
        "0705.0001",
        "2212.99999v65535",
        "2301.00001",
        "2301.00001v1",
        "0101.00001",
        "0703.00001",
    ]);
}

fn assert_validation_round_trip(id_sc: &str, id: &str) {
    let a = ArticleId::from_str(id).unwrap();
    let b = ArticleId::from_str(id_sc).unwrap();
    let c = Validated::parse(id).unwrap();
    let d = Validated::parse(id_sc).unwrap();
    assert_eq!(c, d);

    assert_eq!(a, (&c).into());
    assert_eq!(a, (&d).into());
    assert_eq!(b, (&c).into());
    assert_eq!(b, (&d).into());

    assert_eq!(a.to_string(), b.to_string());
    assert_eq!(b.to_string(), c.to_string());
    assert_eq!(c.to_string(), d.to_string());
    assert_eq!(d.to_string(), a.to_string());

    assert_eq!(a.to_string(), a.identifier());
    assert_eq!(b.to_string(), b.identifier());
    assert_eq!(c.to_string(), c.identifier());
    assert_eq!(d.to_string(), d.identifier());

    assert_eq!(id, Validated::<String>::from(a).to_string());
    assert_eq!(id, Validated::<String>::from(b).to_string());
}

#[test]
fn test_new_id() {
    fn assert_ok(id: &str, year: u16, month: u8, number: u32, version: Option<NonZero<u16>>) {
        assert_validation_round_trip(id, id);

        // check the fields
        let new_id = ArticleId::from_str(id).unwrap();
        assert_eq!(new_id.year(), year);
        assert_eq!(new_id.month(), month);
        assert_eq!(new_id.number().get(), number);
        assert_eq!(new_id.version(), version);

        // check that it displays in the same way
        let displayed = new_id.to_string();
        assert_eq!(id, displayed);
        assert!(displayed.len() <= MAX_ID_FORMATTED_LEN);
        assert_eq!(displayed.len(), new_id.formatted_len());

        // check that it is equal to constructing from parameters
        assert_eq!(
            Ok(new_id),
            ArticleId::new(year, month, None, NonZero::new(number).unwrap(), version)
        );

        let ser = new_id.serialize();
        // check round-trip (de)serialization
        assert_eq!(ArticleId::deserialize(ser), Some(new_id));

        // check correctness of bitmask
        assert_eq!(ser, ser & ArticleId::SERIALIZED_BITMASK);
    }
    assert_ok("1304.0567", 2013, 4, 567, None);
    assert_ok("1304.0001v12", 2013, 4, 1, NonZero::new(12));
    assert_ok("0704.0001v1", 2007, 4, 1, NonZero::new(1));
    assert_ok("1412.7878", 2014, 12, 7878, None);
    assert_ok("1501.00001", 2015, 1, 1, None);
    assert_ok("0703.99999v255", 2107, 3, 99999, NonZero::new(255));
    assert_ok("0001.00001", 2100, 1, 1, None);

    assert!(ArticleId::from_str("").is_err());
    assert!(ArticleId::from_str("13").is_err());
    assert!(ArticleId::from_str("0703.9999").is_err());
    assert!(ArticleId::from_str("0703.99999v").is_err());
    assert!(ArticleId::from_str("0703.99999v0").is_err());
    assert!(ArticleId::from_str("0704.99999").is_err());
}

#[test]
fn test_old_id() {
    fn assert_fields(
        id: ArticleId,
        archive: Archive,
        year: u16,
        month: u8,
        number: u32,
        version: Option<NonZero<u16>>,
    ) {
        println!("{id}");
        // check the fields
        assert_eq!(id.archive(), Some(archive));
        assert_eq!(id.year(), year);
        assert_eq!(id.month(), month);
        assert_eq!(id.number().get(), number.into());
        assert_eq!(id.version(), version);
    }

    fn assert_ok(
        id: &str,
        archive: Archive,
        year: u16,
        month: u8,
        number: u32,
        version: Option<NonZero<u16>>,
    ) {
        let old_id = ArticleId::from_str(id).unwrap();

        assert_fields(old_id, archive, year, month, number, version);

        assert_validation_round_trip(id, id);

        // check that it displays in the same way
        let displayed = old_id.to_string();
        assert_eq!(id, displayed);
        assert!(displayed.len() <= MAX_ID_FORMATTED_LEN);
        assert_eq!(displayed.len(), old_id.formatted_len());

        let ser = old_id.serialize();
        // check round-trip (de)serialization
        assert_eq!(ArticleId::deserialize(ser), Some(old_id));

        // check correctness of bitmask
        assert_eq!(ser, ser & ArticleId::SERIALIZED_BITMASK);

        // check that it is equal to constructing from parameters
        assert_eq!(
            Ok(old_id),
            ArticleId::new(
                year,
                month,
                Some(archive),
                NonZero::new(number).unwrap(),
                version
            )
        );
    }

    assert_ok("math/9205123", Archive::Math, 1992, 5, 123, None);
    assert_ok(
        "hep-lat/9108001v1",
        Archive::HepLat,
        1991,
        8,
        1,
        NonZero::new(1),
    );
    assert_ok(
        "cond-mat/0703999v2",
        Archive::CondMat,
        2007,
        3,
        999,
        NonZero::new(2),
    );
    assert_ok("gr-qc/0703001", Archive::GrQc, 2007, 3, 1, None);
    assert_ok("chao-dyn/0012001", Archive::ChaoDyn, 2000, 12, 1, None);
    assert_ok("supr-con/0001001", Archive::SuprCon, 2000, 1, 1, None);
    assert_ok("acc-phys/0001001", Archive::AccPhys, 2000, 1, 1, None);
    assert_ok(
        "acc-phys/0001001v10000",
        Archive::AccPhys,
        2000,
        1,
        1,
        NonZero::new(10000),
    );
    for i in 1u8..=34u8 {
        // SAFETY: this is the #[repr(u8)] range
        let archive = unsafe { std::mem::transmute::<u8, Archive>(i) };
        let s = archive.to_id().to_owned() + "/9108001v65535";
        assert_ok(&s, archive, 1991, 8, 1, NonZero::new(65535));

        for v in [1, 9, 10, 99, 100, 999, 1000, 9999, 10000, 65535] {
            let tail = format!("/0001999v{v}");
            let s = archive.to_id().to_owned() + &tail;
            assert_ok(&s, archive, 2000, 1, 999, NonZero::new(v));
        }

        let s = archive.to_id().to_owned() + "/0001999";
        assert_ok(&s, archive, 2000, 1, 999, NonZero::new(0));

        let s1 = archive.to_id().to_owned() + ".XY";
        let s2 = archive.to_id().to_owned() + ".UV";
        let s3 = archive.to_id().to_owned();
        assert_eq!(
            Validated::parse(format!("{s1}/9108010v65535")).unwrap(),
            Validated::parse(&format!("{s2}/9108010v65535")).unwrap(),
        );
        assert_validation_round_trip(&format!("{s1}/9310001"), &format!("{s3}/9310001"));
    }
    assert!(ArticleId::from_str("hep-lat.ZZ/9108001v65535").is_ok());

    // check that the subject class is pruned correctly
    assert_validation_round_trip("math.CA/9310001", "math/9310001");
    assert_validation_round_trip("nlin.ZZ/0101010v1", "nlin/0101010v1");
    assert_validation_round_trip("nlin.ZZ/0101010", "nlin/0101010");
    assert_validation_round_trip("nlin.ZZ/9108010", "nlin/9108010");
    assert_validation_round_trip("nlin.ZZ/9108010v65535", "nlin/9108010v65535");

    assert!(ArticleId::from_str("nlin.Z/0101010v1").is_err());
    assert!(ArticleId::from_str("nlin.zz/0101010v1").is_err());
    assert!(ArticleId::from_str("nlin./0101010v1").is_err());
    assert!(ArticleId::from_str("./0101010v1").is_err());
    assert!(ArticleId::from_str("a./0101010v1").is_err());
    assert!(ArticleId::from_str("a/0101010v1").is_err());
    assert!(ArticleId::from_str("a\\0101010v1").is_err());
    assert!(ArticleId::from_str("a.0101010v1").is_err());
    assert!(ArticleId::from_str("0101010v1").is_err());

    assert!(ArticleId::from_str("").is_err());
    assert!(ArticleId::from_str("nlin.ZZ/0101010v0").is_err());
    assert!(ArticleId::from_str("bad/0101010v0").is_err());

    assert!(ArticleId::from_str("hep-lat.ZZ/9108001").is_ok());
    assert!(ArticleId::from_str("hep-lat.ZZ/9107001").is_err());
    assert!(ArticleId::from_str("hep-lat/9107001").is_err());
    assert!(ArticleId::from_str("hep-lat.ZZ/910801").is_err());
    assert!(ArticleId::from_str("hep-lat.ZZ/9108000").is_err());
    assert!(ArticleId::from_str("hep-lat/910801").is_err());
    assert!(ArticleId::from_str("hep-lat/9108000").is_err());
}

#[test]
fn test_archive() {
    use Archive::*;
    for variant in [
        AccPhys, AdapOrg, AlgGeom, AoSci, AstroPh, AtomPh, BayesAn, ChaoDyn, ChemPh, CmpLg,
        CompGas, CondMat, Cs, DgGa, FunctAn, GrQc, HepEx, HepLat, HepPh, HepTh, Math, MathPh,
        MtrlTh, Nlin, NuclEx, NuclTh, PattSol, Physics, PlasmPh, QAlg, QBio, QuantPh, SolvInt,
        SuprCon,
    ] {
        assert_eq!(Archive::from_id(variant.to_id()), Some(variant));
    }

    assert!(Archive::from_id("").is_none());
    assert!(Archive::from_id("nuclex").is_none());
    assert!(Archive::from_id(" ").is_none());
    assert!(Archive::from_id(" math").is_none());
    assert!(Archive::from_id("ma").is_none());
}