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
91
92
93
94
95
96
97
98
99
100
101
102
103
use nom::{
    bytes::complete::tag,
    character::streaming::space1,
    combinator::map,
    sequence::{terminated, tuple},
    IResult,
};

use crate::account::{account, Account};
use crate::date::date;
#[cfg(all(test, feature = "unstable"))]
use crate::pest_parser::Pair;
use crate::Date;
use crate::{amount::amount, Amount};

/// Account balance assertion directive
#[derive(Clone, Debug, PartialEq)]
pub struct Assertion<'a> {
    date: Date,
    account: Account<'a>,
    amount: Amount<'a>,
}

impl<'a> Assertion<'a> {
    /// Date at which the assertion is calculated
    #[must_use]
    pub fn date(&self) -> Date {
        self.date
    }

    /// Account to test
    #[must_use]
    pub fn account(&self) -> &Account<'a> {
        &self.account
    }

    /// Expected balance amount
    #[must_use]
    pub fn amount(&self) -> &Amount<'a> {
        &self.amount
    }

    #[cfg(all(test, feature = "unstable"))]
    pub(crate) fn from_pair(pair: Pair<'a>) -> Self {
        let mut inner = pair.into_inner();
        let date = Date::from_pair(inner.next().expect("no date in balance assertion"));
        let account = Account::from_pair(inner.next().expect("no account in balance assertion"));
        let amount = Amount::from_pair(inner.next().expect("no amount in balance assertion"));
        Self {
            date,
            account,
            amount,
        }
    }
}

pub(crate) fn assertion(input: &str) -> IResult<&str, Assertion<'_>> {
    map(
        tuple((
            terminated(date, tuple((space1, tag("balance"), space1))),
            terminated(account, space1),
            amount,
        )),
        |(date, account, amount)| Assertion {
            date,
            account,
            amount,
        },
    )(input)
}

#[cfg(test)]
mod tests {
    use nom::combinator::all_consuming;

    use crate::account::Type;

    use super::*;

    #[test]
    fn valid_assertion() {
        let input = "2014-01-01 balance Assets:Unknown 1 USD";
        let r = all_consuming(assertion)(input);
        assert_eq!(
            r,
            Ok((
                "",
                Assertion {
                    date: Date::new(2014, 1, 1),
                    account: Account::new(Type::Assets, ["Unknown"]),
                    amount: Amount::new(1, "USD")
                }
            ))
        );
    }

    #[test]
    fn invalid_assertion() {
        let input = "2014-01-01 balance";
        let p = all_consuming(assertion)(input);
        assert!(p.is_err());
    }
}