use crate::error::{PubMedError, Result};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PubMedId {
value: u32,
}
impl PubMedId {
pub fn parse(s: &str) -> Result<Self> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Err(PubMedError::InvalidPmid {
pmid: s.to_string(),
});
}
let value = trimmed
.parse::<u32>()
.map_err(|_| PubMedError::InvalidPmid {
pmid: s.to_string(),
})?;
if value == 0 {
return Err(PubMedError::InvalidPmid {
pmid: s.to_string(),
});
}
Ok(Self { value })
}
pub fn from_u32(value: u32) -> Self {
assert!(value > 0, "PMID must be greater than zero");
Self { value }
}
pub fn try_from_u32(value: u32) -> Result<Self> {
if value == 0 {
return Err(PubMedError::InvalidPmid {
pmid: value.to_string(),
});
}
Ok(Self { value })
}
pub fn as_u32(&self) -> u32 {
self.value
}
pub fn as_str(&self) -> String {
self.value.to_string()
}
}
impl fmt::Display for PubMedId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
impl FromStr for PubMedId {
type Err = PubMedError;
fn from_str(s: &str) -> Result<Self> {
Self::parse(s)
}
}
impl From<u32> for PubMedId {
fn from(value: u32) -> Self {
Self::from_u32(value)
}
}
impl From<PubMedId> for u32 {
fn from(pmid: PubMedId) -> Self {
pmid.value
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PmcId {
value: u32,
}
impl PmcId {
pub fn parse(s: &str) -> Result<Self> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Err(PubMedError::InvalidPmcid {
pmcid: s.to_string(),
});
}
let numeric_part = if trimmed.len() >= 3 && trimmed[0..3].eq_ignore_ascii_case("PMC") {
&trimmed[3..]
} else {
trimmed
};
if numeric_part.is_empty() {
return Err(PubMedError::InvalidPmcid {
pmcid: s.to_string(),
});
}
let value = numeric_part
.parse::<u32>()
.map_err(|_| PubMedError::InvalidPmcid {
pmcid: s.to_string(),
})?;
if value == 0 {
return Err(PubMedError::InvalidPmcid {
pmcid: s.to_string(),
});
}
Ok(Self { value })
}
pub fn from_u32(value: u32) -> Self {
assert!(value > 0, "PMC ID numeric part must be greater than zero");
Self { value }
}
pub fn try_from_u32(value: u32) -> Result<Self> {
if value == 0 {
return Err(PubMedError::InvalidPmcid {
pmcid: value.to_string(),
});
}
Ok(Self { value })
}
pub fn as_str(&self) -> String {
format!("PMC{}", self.value)
}
pub fn numeric_part(&self) -> u32 {
self.value
}
pub fn numeric_part_str(&self) -> String {
self.value.to_string()
}
}
impl fmt::Display for PmcId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PMC{}", self.value)
}
}
impl FromStr for PmcId {
type Err = PubMedError;
fn from_str(s: &str) -> Result<Self> {
Self::parse(s)
}
}
impl From<u32> for PmcId {
fn from(value: u32) -> Self {
Self::from_u32(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pubmedid_parse_valid() {
let pmid = PubMedId::parse("31978945").unwrap();
assert_eq!(pmid.as_u32(), 31978945);
assert_eq!(pmid.as_str(), "31978945");
}
#[test]
fn test_pubmedid_parse_with_whitespace() {
let pmid = PubMedId::parse(" 31978945 ").unwrap();
assert_eq!(pmid.as_u32(), 31978945);
}
#[test]
fn test_pubmedid_parse_empty() {
assert!(PubMedId::parse("").is_err());
assert!(PubMedId::parse(" ").is_err());
}
#[test]
fn test_pubmedid_parse_non_numeric() {
assert!(PubMedId::parse("abc").is_err());
assert!(PubMedId::parse("123abc").is_err());
assert!(PubMedId::parse("12.34").is_err());
}
#[test]
fn test_pubmedid_parse_zero() {
assert!(PubMedId::parse("0").is_err());
}
#[test]
fn test_pubmedid_parse_negative() {
assert!(PubMedId::parse("-123").is_err());
}
#[test]
fn test_pubmedid_from_u32() {
let pmid = PubMedId::from_u32(31978945);
assert_eq!(pmid.as_u32(), 31978945);
}
#[test]
#[should_panic(expected = "PMID must be greater than zero")]
fn test_pubmedid_from_u32_zero_panics() {
PubMedId::from_u32(0);
}
#[test]
fn test_pubmedid_try_from_u32() {
let pmid = PubMedId::try_from_u32(31978945).unwrap();
assert_eq!(pmid.as_u32(), 31978945);
assert!(PubMedId::try_from_u32(0).is_err());
}
#[test]
fn test_pubmedid_display() {
let pmid = PubMedId::from_u32(31978945);
assert_eq!(format!("{}", pmid), "31978945");
}
#[test]
fn test_pubmedid_from_str_trait() {
let pmid: PubMedId = "31978945".parse().unwrap();
assert_eq!(pmid.as_u32(), 31978945);
}
#[test]
fn test_pubmedid_conversions() {
let pmid = PubMedId::from_u32(31978945);
let value: u32 = pmid.into();
assert_eq!(value, 31978945);
}
#[test]
fn test_pmcid_parse_with_prefix() {
let pmcid = PmcId::parse("PMC7906746").unwrap();
assert_eq!(pmcid.as_str(), "PMC7906746");
assert_eq!(pmcid.numeric_part(), 7906746);
}
#[test]
fn test_pmcid_parse_without_prefix() {
let pmcid = PmcId::parse("7906746").unwrap();
assert_eq!(pmcid.as_str(), "PMC7906746");
assert_eq!(pmcid.numeric_part(), 7906746);
}
#[test]
fn test_pmcid_parse_case_insensitive() {
let pmcid1 = PmcId::parse("pmc7906746").unwrap();
let pmcid2 = PmcId::parse("Pmc7906746").unwrap();
let pmcid3 = PmcId::parse("PMC7906746").unwrap();
assert_eq!(pmcid1, pmcid2);
assert_eq!(pmcid2, pmcid3);
assert_eq!(pmcid1.as_str(), "PMC7906746");
}
#[test]
fn test_pmcid_parse_with_whitespace() {
let pmcid = PmcId::parse(" PMC7906746 ").unwrap();
assert_eq!(pmcid.as_str(), "PMC7906746");
let pmcid = PmcId::parse(" 7906746 ").unwrap();
assert_eq!(pmcid.as_str(), "PMC7906746");
}
#[test]
fn test_pmcid_parse_empty() {
assert!(PmcId::parse("").is_err());
assert!(PmcId::parse(" ").is_err());
assert!(PmcId::parse("PMC").is_err());
}
#[test]
fn test_pmcid_parse_non_numeric() {
assert!(PmcId::parse("PMCabc").is_err());
assert!(PmcId::parse("PMC123abc").is_err());
assert!(PmcId::parse("abc").is_err());
}
#[test]
fn test_pmcid_parse_zero() {
assert!(PmcId::parse("PMC0").is_err());
assert!(PmcId::parse("0").is_err());
}
#[test]
fn test_pmcid_from_u32() {
let pmcid = PmcId::from_u32(7906746);
assert_eq!(pmcid.as_str(), "PMC7906746");
assert_eq!(pmcid.numeric_part(), 7906746);
}
#[test]
#[should_panic(expected = "PMC ID numeric part must be greater than zero")]
fn test_pmcid_from_u32_zero_panics() {
PmcId::from_u32(0);
}
#[test]
fn test_pmcid_try_from_u32() {
let pmcid = PmcId::try_from_u32(7906746).unwrap();
assert_eq!(pmcid.numeric_part(), 7906746);
assert!(PmcId::try_from_u32(0).is_err());
}
#[test]
fn test_pmcid_numeric_part_str() {
let pmcid = PmcId::parse("PMC7906746").unwrap();
assert_eq!(pmcid.numeric_part_str(), "7906746");
}
#[test]
fn test_pmcid_display() {
let pmcid = PmcId::from_u32(7906746);
assert_eq!(format!("{}", pmcid), "PMC7906746");
}
#[test]
fn test_pmcid_from_str_trait() {
let pmcid: PmcId = "PMC7906746".parse().unwrap();
assert_eq!(pmcid.numeric_part(), 7906746);
let pmcid: PmcId = "7906746".parse().unwrap();
assert_eq!(pmcid.as_str(), "PMC7906746");
}
#[test]
fn test_pmcid_equality() {
let pmcid1 = PmcId::parse("PMC7906746").unwrap();
let pmcid2 = PmcId::parse("7906746").unwrap();
let pmcid3 = PmcId::from_u32(7906746);
assert_eq!(pmcid1, pmcid2);
assert_eq!(pmcid2, pmcid3);
}
#[test]
fn test_pmcid_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(PmcId::parse("PMC7906746").unwrap());
set.insert(PmcId::parse("7906746").unwrap());
assert_eq!(set.len(), 1);
}
#[test]
fn test_real_world_pmids() {
let test_cases = vec![
"31978945", "25760099", "33515491", "12345678",
];
for pmid_str in test_cases {
let pmid = PubMedId::parse(pmid_str).unwrap();
assert_eq!(pmid.as_str(), pmid_str);
}
}
#[test]
fn test_real_world_pmcids() {
let test_cases = vec![
("PMC7906746", "PMC7906746"),
("PMC10618641", "PMC10618641"),
("PMC10000000", "PMC10000000"),
("7906746", "PMC7906746"), ("10618641", "PMC10618641"), ];
for (input, expected) in test_cases {
let pmcid = PmcId::parse(input).unwrap();
assert_eq!(pmcid.as_str(), expected);
}
}
#[test]
fn test_serialization() {
let pmid = PubMedId::from_u32(31978945);
let json = serde_json::to_string(&pmid).unwrap();
let deserialized: PubMedId = serde_json::from_str(&json).unwrap();
assert_eq!(pmid, deserialized);
let pmcid = PmcId::from_u32(7906746);
let json = serde_json::to_string(&pmcid).unwrap();
let deserialized: PmcId = serde_json::from_str(&json).unwrap();
assert_eq!(pmcid, deserialized);
}
}