use std::{fmt::Display, path::Path, str::FromStr};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Sex {
Male,
Female,
#[default]
Unknown,
}
impl FromStr for Sex {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"1" | "m" | "M" => Ok(Sex::Male),
"2" | "f" | "F" => Ok(Sex::Female),
_ => Ok(Sex::Unknown),
}
}
}
impl Display for Sex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Sex::Male => write!(f, "1"),
Sex::Female => write!(f, "2"),
Sex::Unknown => write!(f, "0"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Disease {
Affected,
Unaffected,
#[default]
Unknown,
}
impl FromStr for Disease {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"1" => Ok(Disease::Unaffected),
"2" => Ok(Disease::Affected),
_ => Ok(Disease::Unknown),
}
}
}
impl Display for Disease {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Disease::Unaffected => write!(f, "1"),
Disease::Affected => write!(f, "2"),
Disease::Unknown => write!(f, "0"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Individual {
pub family: String,
pub name: String,
#[serde(with = "string_option")]
pub father: Option<String>,
#[serde(with = "string_option")]
pub mother: Option<String>,
#[serde(with = "string")]
pub sex: Sex,
#[serde(with = "string")]
pub disease: Disease,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Pedigree {
pub individuals: Vec<Individual>,
}
impl Pedigree {
pub fn from_path<P>(path: P) -> Result<Self, anyhow::Error>
where
P: AsRef<Path>,
{
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b'\t')
.comment(Some(b'#'))
.has_headers(false)
.from_path(path)?;
let mut individuals = Vec::new();
for result in rdr.deserialize() {
let individual: Individual = result?;
individuals.push(individual);
}
Ok(Pedigree { individuals })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PedigreeByName {
pub individuals: indexmap::IndexMap<String, Individual>,
}
impl PedigreeByName {
pub fn from_path<P>(path: P) -> Result<Self, anyhow::Error>
where
P: AsRef<Path>,
{
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b'\t')
.comment(Some(b'#'))
.has_headers(false)
.from_path(path)?;
let mut individuals = indexmap::IndexMap::new();
for result in rdr.deserialize() {
let individual: Individual = result?;
individuals.insert(individual.name.clone(), individual);
}
Ok(PedigreeByName { individuals })
}
}
mod string {
use std::fmt::Display;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serializer, de};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}
mod string_option {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(s) => serializer.collect_str(s),
None => serializer.collect_str("0"),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == "0" { Ok(None) } else { Ok(Some(s)) }
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn sex_display() -> Result<(), anyhow::Error> {
assert_eq!(format!("{}", Sex::Unknown), "0");
assert_eq!(format!("{}", Sex::Male), "1");
assert_eq!(format!("{}", Sex::Female), "2");
Ok(())
}
#[test]
fn sex_from_str() -> Result<(), anyhow::Error> {
assert_eq!(Sex::from_str("0")?, Sex::Unknown);
assert_eq!(Sex::from_str("1")?, Sex::Male);
assert_eq!(Sex::from_str("2")?, Sex::Female);
Ok(())
}
#[test]
fn disease_display() -> Result<(), anyhow::Error> {
assert_eq!(format!("{}", Disease::Unknown), "0");
assert_eq!(format!("{}", Disease::Unaffected), "1");
assert_eq!(format!("{}", Disease::Affected), "2");
Ok(())
}
#[test]
fn disease_from_str() -> Result<(), anyhow::Error> {
assert_eq!(Disease::from_str("0")?, Disease::Unknown);
assert_eq!(Disease::from_str("1")?, Disease::Unaffected);
assert_eq!(Disease::from_str("2")?, Disease::Affected);
Ok(())
}
#[test]
fn pedigree_from_path() -> Result<(), anyhow::Error> {
let pedigree = Pedigree::from_path("tests/data/ped/family.ped")?;
assert_eq!(pedigree.individuals.len(), 3);
Ok(())
}
#[test]
fn pedigree_by_name_from_path() -> Result<(), anyhow::Error> {
let pedigree = PedigreeByName::from_path("tests/data/ped/family.ped")?;
assert_eq!(pedigree.individuals.len(), 3);
Ok(())
}
}