use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::error::Error;
macro_rules! schema_enum {
(
$(#[$doc:meta])*
$name:ident: $what:literal {
$($(#[$vdoc:meta])* $variant:ident => $s:literal),+ $(,)?
}
) => {
$(#[$doc])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum $name {
$(
$(#[$vdoc])*
#[serde(rename = $s)]
$variant,
)+
}
impl $name {
pub fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $s,)+
}
}
pub const ALL: &'static [Self] = &[$(Self::$variant),+];
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
$($s => Ok(Self::$variant),)+
other => Err(Error::InvalidValue(format!(
concat!("`{}` is not a valid ", $what, " (expected one of: {})"),
other,
[$($s),+].join(", "),
))),
}
}
}
};
}
schema_enum! {
SampleType: "sample type" {
Unknown => "unkn",
NoTemplateControl => "ntc",
NoAmplificationControl => "nac",
Standard => "std",
NoTargetPresent => "ntp",
MinusRt => "nrt",
PositiveControl => "pos",
OpticalCalibrator => "opt",
}
}
schema_enum! {
TargetType: "target type" {
TargetOfInterest => "toi",
Reference => "ref",
}
}
schema_enum! {
DyeChemistry: "dye chemistry" {
NonSaturatingDnaBindingDye => "non-saturating DNA binding dye",
SaturatingDnaBindingDye => "saturating DNA binding dye",
HybridizationProbe => "hybridization probe",
HydrolysisProbe => "hydrolysis probe",
LabelledForwardPrimer => "labelled forward primer",
LabelledReversePrimer => "labelled reverse primer",
DnaZymeProbe => "DNA-zyme probe",
}
}
schema_enum! {
Measure: "measure" {
RealTime => "real time",
MeltCurve => "meltcurve",
}
}
schema_enum! {
Nucleotide: "nucleotide type" {
Dna => "DNA",
GenomicDna => "genomic DNA",
Cdna => "cDNA",
Rna => "RNA",
}
}
schema_enum! {
LabelFormat: "label format" {
Abc => "ABC",
Numbers => "123",
A1a1 => "A1a1",
}
}
schema_enum! {
PrimingMethod: "priming method" {
OligoDt => "oligo-dt",
Random => "random",
TargetSpecific => "target-specific",
OligoDtAndRandom => "oligo-dt and random",
Other => "other",
}
}
schema_enum! {
QuantityUnit: "quantity unit" {
Copies => "cop",
Fold => "fold",
Dilution => "dil",
Nanogram => "ng",
NanoMol => "nMol",
Other => "other",
}
}
schema_enum! {
CqDetectionMethod: "Cq detection method" {
AutomatedThresholdAndBaseline => "automated threshold and baseline settings",
ManualThresholdAndBaseline => "manual threshold and baseline settings",
SecondDerivativeMaximum => "second derivative maximum",
Other => "other",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_all_values() {
fn check<E: Copy + FromStr<Err = Error> + PartialEq + fmt::Debug>(
all: &[E],
as_str: impl Fn(E) -> &'static str,
) {
for &v in all {
assert_eq!(as_str(v).parse::<E>().unwrap(), v);
}
}
check(SampleType::ALL, SampleType::as_str);
check(TargetType::ALL, TargetType::as_str);
check(DyeChemistry::ALL, DyeChemistry::as_str);
check(Measure::ALL, Measure::as_str);
check(Nucleotide::ALL, Nucleotide::as_str);
check(LabelFormat::ALL, LabelFormat::as_str);
check(PrimingMethod::ALL, PrimingMethod::as_str);
check(QuantityUnit::ALL, QuantityUnit::as_str);
check(CqDetectionMethod::ALL, CqDetectionMethod::as_str);
}
#[test]
fn rejects_unknown_and_wrong_case() {
assert!("Unkn".parse::<SampleType>().is_err());
assert!("TOI".parse::<TargetType>().is_err());
assert!("melt curve".parse::<Measure>().is_err());
assert!("dna".parse::<Nucleotide>().is_err());
}
#[test]
fn serde_uses_schema_strings() {
assert_eq!(
serde_json::to_string(&DyeChemistry::HydrolysisProbe).unwrap(),
"\"hydrolysis probe\""
);
let v: SampleType = serde_json::from_str("\"ntc\"").unwrap();
assert_eq!(v, SampleType::NoTemplateControl);
}
#[test]
fn exact_schema_counts() {
assert_eq!(SampleType::ALL.len(), 8);
assert_eq!(TargetType::ALL.len(), 2);
assert_eq!(DyeChemistry::ALL.len(), 7);
assert_eq!(Measure::ALL.len(), 2);
assert_eq!(Nucleotide::ALL.len(), 4);
assert_eq!(LabelFormat::ALL.len(), 3);
assert_eq!(PrimingMethod::ALL.len(), 5);
assert_eq!(QuantityUnit::ALL.len(), 6);
assert_eq!(CqDetectionMethod::ALL.len(), 4);
}
}