hmrc_rates/rate.rs
1use rust_decimal::Decimal;
2
3use crate::types::{Currency, Period};
4
5/// A resolved HMRC rate: currency units per £1, with exact `Decimal` arithmetic.
6///
7/// The crate never rounds.
8/// Callers apply whatever rounding their tax context requires.
9#[derive(Copy, Clone, PartialEq, Eq, Debug)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Rate {
12 units_per_gbp: Decimal,
13 currency: Currency,
14 period: Period,
15}
16
17impl Rate {
18 pub(crate) fn new(units_per_gbp: Decimal, currency: Currency, period: Period) -> Rate {
19 Rate {
20 units_per_gbp,
21 currency,
22 period,
23 }
24 }
25
26 /// The canonical HMRC figure: how many currency units £1 buys.
27 pub fn units_per_gbp(&self) -> Decimal {
28 self.units_per_gbp
29 }
30
31 /// Converts an amount in this rate's currency to GBP (`amount / units_per_gbp`).
32 ///
33 /// Exact division, round the result yourself.
34 ///
35 /// # Examples
36 ///
37 /// ```
38 /// use hmrc_rates::{YearMonth, Rates};
39 /// use rust_decimal::Decimal;
40 ///
41 /// let rates = Rates::new();
42 /// let usd = rates.monthly_rate("USD", YearMonth::new(2025, 8).unwrap())?;
43 /// let gbp = usd.to_gbp(Decimal::from(2500));
44 /// println!("£{}", gbp.round_dp(2));
45 /// # Ok::<(), hmrc_rates::LookupError>(())
46 /// ```
47 pub fn to_gbp(&self, amount: Decimal) -> Decimal {
48 amount / self.units_per_gbp
49 }
50
51 /// Converts a GBP amount to this rate's currency (`gbp * units_per_gbp`).
52 pub fn from_gbp(&self, gbp: Decimal) -> Decimal {
53 gbp * self.units_per_gbp
54 }
55
56 /// The currency this rate quotes against GBP.
57 pub fn currency(&self) -> Currency {
58 self.currency
59 }
60
61 /// The period the rate was published for.
62 /// Reveals the substituted month after
63 /// [`Rates::monthly_rate_or_earlier`](crate::Rates::monthly_rate_or_earlier).
64 pub fn period(&self) -> Period {
65 self.period
66 }
67}