1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use std::{fmt::Display, ops::Add, str::FromStr};

use crate::{
    error::{Context, CustomError},
    formula::MolecularFormula,
    helper_functions::parse_molecular_formula_pro_forma,
};

/// All possible neutral losses
#[derive(Debug, Clone, PartialEq)]
pub enum NeutralLoss {
    /// Gain of a specific formula
    Gain(MolecularFormula),
    /// Loss of a specific formula
    Loss(MolecularFormula),
}

impl NeutralLoss {
    /// Generate a nice HTML notation for this `NeutralLoss`
    pub fn hill_notation_html(&self) -> String {
        match self {
            Self::Loss(c) => format!("-{}", c.hill_notation_html()),
            Self::Gain(c) => format!("+{}", c.hill_notation_html()),
        }
    }
}

impl FromStr for NeutralLoss {
    type Err = CustomError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(c) = s.chars().next() {
            match c {
                '+' => Ok(Self::Gain(
                    parse_molecular_formula_pro_forma(&s[1..]).map_err(|e| {
                        CustomError::error(
                            "Invalid neutral loss",
                            e,
                            Context::line(0, s, 1, s.len() - 1),
                        )
                    })?,
                )),
                '-' => Ok(Self::Loss(
                    parse_molecular_formula_pro_forma(&s[1..]).map_err(|e| {
                        CustomError::error(
                            "Invalid neutral loss",
                            e,
                            Context::line(0, s, 1, s.len() - 1),
                        )
                    })?,
                )),
                _ => Err(CustomError::error(
                    "Invalid neutral loss",
                    "A neutral loss can only start with '+' or '-'",
                    Context::line(0, s, 0, 1),
                )),
            }
        } else {
            Err(CustomError::error(
                "Invalid neutral loss",
                "A neutral loss cannot be an empty string",
                Context::None,
            ))
        }
    }
}

impl Display for NeutralLoss {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Loss(c) => format!("-{c}"),
                Self::Gain(c) => format!("+{c}"),
            }
        )
    }
}

impl std::ops::Add<&NeutralLoss> for &MolecularFormula {
    type Output = MolecularFormula;
    fn add(self, rhs: &NeutralLoss) -> Self::Output {
        match rhs {
            NeutralLoss::Gain(mol) => self + mol,
            NeutralLoss::Loss(mol) => self - mol,
        }
    }
}

impl_binop_ref_cases!(impl Add, add for MolecularFormula, NeutralLoss, MolecularFormula);