otp_std/otp/
core.rs

1//! One-Time Password (OTP) enums.
2//!
3//! The [`Otp`] enum contains [`Hotp`] and [`Totp`] as its variants.
4
5#[cfg(feature = "auth")]
6use miette::Diagnostic;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[cfg(feature = "auth")]
12use thiserror::Error;
13
14use crate::{base::Base, hotp::Hotp, otp::type_of::Type, totp::Totp};
15
16#[cfg(feature = "auth")]
17use crate::{
18    auth::{query::Query, url::Url},
19    hotp, totp,
20};
21
22/// Represents either [`Hotp`] or [`Totp`] configuration.
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
26pub enum Otp<'o> {
27    /// HOTP configuration.
28    Hotp(Hotp<'o>),
29    /// TOTP configuration.
30    Totp(Totp<'o>),
31}
32
33impl<'o> Otp<'o> {
34    /// Returns the base configuration, regardless of the variant.
35    pub const fn base(&self) -> &Base<'o> {
36        match self {
37            Self::Hotp(hotp) => hotp.base(),
38            Self::Totp(totp) => totp.base(),
39        }
40    }
41
42    /// Returns the mutable base configuration, regardless of the variant.
43    pub fn base_mut(&mut self) -> &mut Base<'o> {
44        match self {
45            Self::Hotp(hotp) => hotp.base_mut(),
46            Self::Totp(totp) => totp.base_mut(),
47        }
48    }
49
50    /// Consumes [`Self`], returning the base configuration, regardless of the variant.
51    pub fn into_base(self) -> Base<'o> {
52        match self {
53            Self::Hotp(hotp) => hotp.into_base(),
54            Self::Totp(totp) => totp.into_base(),
55        }
56    }
57}
58
59impl Otp<'_> {
60    /// Returns the [`Type`] of this OTP configuration.
61    pub const fn type_of(&self) -> Type {
62        match self {
63            Self::Hotp(_) => Type::Hotp,
64            Self::Totp(_) => Type::Totp,
65        }
66    }
67}
68
69/// Represents sources of errors that can occur when extracting OTP configurations from URLs.
70#[cfg(feature = "auth")]
71#[derive(Debug, Error, Diagnostic)]
72#[error(transparent)]
73#[diagnostic(transparent)]
74pub enum ErrorSource {
75    /// The HOTP configuration could not be extracted.
76    Hotp(#[from] hotp::Error),
77    /// The TOTP configuration could not be extracted.
78    Totp(#[from] totp::Error),
79}
80
81/// Represents errors that can occur when extracting OTP configurations from URLs.
82#[cfg(feature = "auth")]
83#[derive(Debug, Error, Diagnostic)]
84#[error("failed to extract OTP from URL")]
85#[diagnostic(code(otp_std::otp), help("see the report for more information"))]
86pub struct Error {
87    /// The source of this error.
88    #[source]
89    #[diagnostic_source]
90    pub source: ErrorSource,
91}
92
93#[cfg(feature = "auth")]
94impl Error {
95    /// Constructs [`Self`].
96    pub const fn new(source: ErrorSource) -> Self {
97        Self { source }
98    }
99
100    /// Constructs [`Self`] from [`hotp::Error`].
101    pub fn hotp(error: hotp::Error) -> Self {
102        Self::new(error.into())
103    }
104
105    /// Constructs [`Self`] from [`totp::Error`].
106    pub fn totp(error: totp::Error) -> Self {
107        Self::new(error.into())
108    }
109}
110
111#[cfg(feature = "auth")]
112impl Otp<'_> {
113    /// Applies [`Self`] to the given [`Url`].
114    pub fn query_for(&self, url: &mut Url) {
115        match self {
116            Self::Hotp(hotp) => hotp.query_for(url),
117            Self::Totp(totp) => totp.query_for(url),
118        }
119    }
120
121    /// Extracts [`Self`] from the given [`Query`].
122    ///
123    /// # Errors
124    ///
125    /// Returns [`struct@Error`] when the OTP configuration can not be extracted.
126    pub fn extract_from(query: &mut Query<'_>, type_of: Type) -> Result<Self, Error> {
127        match type_of {
128            Type::Hotp => Hotp::extract_from(query)
129                .map(Self::Hotp)
130                .map_err(Error::hotp),
131
132            Type::Totp => Totp::extract_from(query)
133                .map(Self::Totp)
134                .map_err(Error::totp),
135        }
136    }
137}
138
139impl<'h> From<Hotp<'h>> for Otp<'h> {
140    fn from(hotp: Hotp<'h>) -> Self {
141        Self::Hotp(hotp)
142    }
143}
144
145impl<'t> From<Totp<'t>> for Otp<'t> {
146    fn from(totp: Totp<'t>) -> Self {
147        Self::Totp(totp)
148    }
149}
150
151/// Represents owned [`Otp`].
152pub type Owned = Otp<'static>;
153
154impl Otp<'_> {
155    /// Converts [`Self`] into [`Owned`].
156    pub fn into_owned(self) -> Owned {
157        match self {
158            Self::Hotp(hotp) => Owned::Hotp(hotp.into_owned()),
159            Self::Totp(totp) => Owned::Totp(totp.into_owned()),
160        }
161    }
162}