use std::{fmt, str::FromStr};
use miette::Diagnostic;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use crate::macros::errors;
#[cfg(feature = "auth")]
use crate::auth::url::Url;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Type {
Hotp,
Totp,
}
#[cfg(feature = "serde")]
impl Serialize for Type {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.static_str().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Type {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let string = <&str>::deserialize(deserializer)?;
string.parse().map_err(de::Error::custom)
}
}
pub const HOTP: &str = "hotp";
pub const TOTP: &str = "totp";
impl Type {
pub const fn static_str(&self) -> &'static str {
match self {
Self::Hotp => HOTP,
Self::Totp => TOTP,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.static_str().fmt(formatter)
}
}
#[derive(Debug, Error, Diagnostic)]
#[error("failed to parse `{string}` into type; expected either `{HOTP}` or `{TOTP}`")]
#[diagnostic(
code(otp_std::otp::type_of::parse),
help("see the report for more information")
)]
pub struct ParseError {
pub string: String,
}
impl ParseError {
pub const fn new(string: String) -> Self {
Self { string }
}
}
#[cfg(feature = "auth")]
#[derive(Debug, Error, Diagnostic)]
#[error("failed to find OTP type")]
#[diagnostic(
code(otp_std::otp::type_of::not_found),
help("see the report for more information")
)]
pub struct NotFoundError;
#[cfg(feature = "auth")]
#[derive(Debug, Error, Diagnostic)]
#[error(transparent)]
#[diagnostic(transparent)]
pub enum ErrorSource {
NotFound(#[from] NotFoundError),
Parse(#[from] ParseError),
}
#[cfg(feature = "auth")]
#[derive(Debug, Error, Diagnostic)]
#[error("failed to extract type from OTP URL")]
#[diagnostic(
code(otp_std::otp::type_of),
help("see the report for more information")
)]
pub struct Error {
#[source]
#[diagnostic_source]
pub source: ErrorSource,
}
#[cfg(feature = "auth")]
impl Error {
pub const fn new(source: ErrorSource) -> Self {
Self { source }
}
pub fn not_found(error: NotFoundError) -> Self {
Self::new(error.into())
}
pub fn new_not_found() -> Self {
Self::not_found(NotFoundError)
}
pub fn parse(error: ParseError) -> Self {
Self::new(error.into())
}
}
#[cfg(feature = "auth")]
errors! {
Type = Error,
Hack = $,
not_found_error => new_not_found(),
parse_error => parse(error),
}
#[cfg(feature = "auth")]
impl Type {
pub fn extract_from(url: &Url) -> Result<Self, Error> {
let host = url.host_str().ok_or_else(|| not_found_error!())?;
host.parse().map_err(|error| parse_error!(error))
}
}
errors! {
Type = ParseError,
Hack = $,
error => new(string => to_owned),
}
impl FromStr for Type {
type Err = ParseError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
HOTP => Ok(Self::Hotp),
TOTP => Ok(Self::Totp),
_ => Err(error!(string)),
}
}
}