otp_std/
digits.rs

1//! One-Time Password (OTP) digits.
2
3use std::{fmt, str::FromStr};
4
5use const_macros::{const_early, const_ok, const_try};
6
7use miette::Diagnostic;
8
9#[cfg(feature = "serde")]
10use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
11
12use thiserror::Error;
13
14use crate::{int, macros::errors};
15
16/// The minimum digits value.
17pub const MIN: u8 = 6;
18
19/// The maximum digits value.
20pub const MAX: u8 = 8;
21
22/// The default digits value.
23pub const DEFAULT: u8 = MIN;
24
25/// Represents errors that can occur during digits creation.
26///
27/// This error is returned when the given value is less than [`MIN`] or greater than [`MAX`].
28#[derive(Debug, Error, Diagnostic)]
29#[error("expected digits in `[{MIN}, {MAX}]` range, got `{value}`")]
30#[diagnostic(
31    code(otp_std::digits),
32    help("make sure the digits are at least `{MIN}` and at most `{MAX}`")
33)]
34pub struct Error {
35    /// The invalid value.
36    pub value: u8,
37}
38
39impl Error {
40    /// Constructs [`Self`].
41    pub const fn new(value: u8) -> Self {
42        Self { value }
43    }
44}
45
46/// Represents sources of errors that can occur when parsing [`Digits`] values.
47#[derive(Debug, Error, Diagnostic)]
48#[error(transparent)]
49#[diagnostic(transparent)]
50pub enum ParseErrorSource {
51    /// Invalid digits value.
52    Digits(#[from] Error),
53    /// Integer parse error.
54    Int(#[from] int::ParseError),
55}
56
57/// Represents errors that occur when parsing [`Digits`] values.
58#[derive(Debug, Error, Diagnostic)]
59#[error("failed to parse `{string}` to digits")]
60#[diagnostic(
61    code(otp_std::digits::parse),
62    help("see the report for more information")
63)]
64pub struct ParseError {
65    /// The source of this error.
66    #[source]
67    #[diagnostic_source]
68    pub source: ParseErrorSource,
69    /// The string that could not be parsed.
70    pub string: String,
71}
72
73impl ParseError {
74    /// Constructs [`Self`].
75    pub const fn new(source: ParseErrorSource, string: String) -> Self {
76        Self { source, string }
77    }
78
79    /// Constructs [`Self`] from [`struct@Error`].
80    pub fn digits(error: Error, string: String) -> Self {
81        Self::new(error.into(), string)
82    }
83
84    /// Constructs [`Self`] from [`int::ParseError`].
85    pub fn int(error: int::ParseError, string: String) -> Self {
86        Self::new(error.into(), string)
87    }
88}
89
90/// Represents the number of digits in OTPs.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub struct Digits {
93    value: u8,
94}
95
96#[cfg(feature = "serde")]
97impl Serialize for Digits {
98    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
99        self.get().serialize(serializer)
100    }
101}
102
103#[cfg(feature = "serde")]
104impl<'de> Deserialize<'de> for Digits {
105    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
106        let value = u8::deserialize(deserializer)?;
107
108        Self::new(value).map_err(de::Error::custom)
109    }
110}
111
112errors! {
113    Type = ParseError,
114    Hack = $,
115    digits_error => digits(error, string => to_owned),
116    int_error => int(error, string => to_owned),
117}
118
119impl FromStr for Digits {
120    type Err = ParseError;
121
122    fn from_str(string: &str) -> Result<Self, Self::Err> {
123        let value = string
124            .parse()
125            .map_err(|error| int_error!(int::wrap(error), string))?;
126
127        Self::new(value).map_err(|error| digits_error!(error, string))
128    }
129}
130
131impl fmt::Display for Digits {
132    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133        self.get().fmt(formatter)
134    }
135}
136
137impl TryFrom<u8> for Digits {
138    type Error = Error;
139
140    fn try_from(value: u8) -> Result<Self, Self::Error> {
141        Self::new(value)
142    }
143}
144
145impl From<Digits> for u8 {
146    fn from(digits: Digits) -> Self {
147        digits.get()
148    }
149}
150
151impl Default for Digits {
152    fn default() -> Self {
153        Self::DEFAULT
154    }
155}
156
157errors! {
158    Type = Error,
159    Hack = $,
160    error => new(value),
161}
162
163impl Digits {
164    /// Constructs [`Self`], if possible.
165    ///
166    /// # Errors
167    ///
168    /// See [`check`] for more information.
169    ///
170    /// [`check`]: Self::check
171    pub const fn new(value: u8) -> Result<Self, Error> {
172        const_try!(Self::check(value));
173
174        // SAFETY: the value is in the valid range for `Self`
175        Ok(unsafe { Self::new_unchecked(value) })
176    }
177
178    /// Similar to [`new`], but the error is discarded.
179    ///
180    /// [`new`]: Self::new
181    pub const fn new_ok(value: u8) -> Option<Self> {
182        const_ok!(Self::new(value))
183    }
184
185    /// Checks if the provided value is valid for [`Self`].
186    ///
187    /// # Errors
188    ///
189    /// Returns [`struct@Error`] if the given value is less than [`MIN`] or greater than [`MAX`].
190    pub const fn check(value: u8) -> Result<(), Error> {
191        const_early!(value < MIN || value > MAX => error!(value));
192
193        Ok(())
194    }
195
196    /// Constructs [`Self`] without checking the given value.
197    ///
198    /// # Safety
199    ///
200    /// The value must be greater than or equal to [`MIN`] and less than or equal to [`MAX`].
201    ///
202    /// This invariant can be checked using [`check`].
203    ///
204    /// [`check`]: Self::check
205    pub const unsafe fn new_unchecked(value: u8) -> Self {
206        Self { value }
207    }
208
209    /// The minimum [`Self`] value.
210    pub const MIN: Self = Self::new_ok(MIN).unwrap();
211
212    /// The maximum [`Self`] value.
213    pub const MAX: Self = Self::new_ok(MAX).unwrap();
214
215    /// The default [`Self`] value.
216    pub const DEFAULT: Self = Self::new_ok(DEFAULT).unwrap();
217
218    /// Returns the value wrapped in [`Self`] as [`usize`].
219    pub const fn count(self) -> usize {
220        self.get() as usize
221    }
222
223    /// Returns the value wrapped in [`Self`].
224    pub const fn get(self) -> u8 {
225        self.value
226    }
227
228    /// Raises `10` to the power of the value wrapped in [`Self`].
229    pub const fn power(self) -> u32 {
230        10u32.pow(self.get() as u32)
231    }
232
233    /// Formats the given code, padding it to the length returned from [`count`].
234    ///
235    /// [`count`]: Self::count
236    pub fn string(self, code: u32) -> String {
237        format!("{code:0count$}", count = self.count())
238    }
239}