use bon::Builder;
use miette::Diagnostic;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
auth::{
self,
label::{self, Label},
query::Query,
scheme,
url::{self, Url},
},
macros::errors,
otp::{
self,
core::Otp,
type_of::{self, Type},
},
};
pub const SCHEME: &str = "otpauth";
pub const BASE_URL_ALWAYS_VALID: &str = "OTP base URL is always valid";
#[derive(Debug, Clone, PartialEq, Eq, Hash, Builder)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Auth<'a> {
#[builder(into)]
pub otp: Otp<'a>,
pub label: Label<'a>,
}
#[derive(Debug, Error, Diagnostic)]
#[error(transparent)]
#[diagnostic(transparent)]
pub enum ErrorSource {
Url(#[from] url::Error),
Scheme(#[from] scheme::Error),
TypeOf(#[from] type_of::Error),
Label(#[from] label::Error),
Otp(#[from] otp::core::Error),
}
#[derive(Debug, Error, Diagnostic)]
#[error("failed to extract auth from `{string}`")]
#[diagnostic(code(otp_std::auth::core), help("see the report for more information"))]
pub struct Error {
#[source]
#[diagnostic_source]
pub source: ErrorSource,
pub string: String,
}
impl Error {
pub const fn new(source: ErrorSource, string: String) -> Self {
Self { source, string }
}
pub fn parse(error: url::Error, string: String) -> Self {
Self::new(error.into(), string)
}
pub fn scheme(error: scheme::Error, string: String) -> Self {
Self::new(error.into(), string)
}
pub fn type_of(error: type_of::Error, string: String) -> Self {
Self::new(error.into(), string)
}
pub fn label(error: label::Error, string: String) -> Self {
Self::new(error.into(), string)
}
pub fn otp(error: otp::core::Error, string: String) -> Self {
Self::new(error.into(), string)
}
}
impl Auth<'_> {
pub const fn otp(&self) -> &Otp<'_> {
&self.otp
}
pub const fn label(&self) -> &Label<'_> {
&self.label
}
}
pub type Parts<'p> = (Otp<'p>, Label<'p>);
pub type OwnedParts = Parts<'static>;
impl<'a> Auth<'a> {
pub fn from_parts(parts: Parts<'a>) -> Self {
let (otp, label) = parts;
Self::builder().otp(otp).label(label).build()
}
pub fn into_parts(self) -> Parts<'a> {
(self.otp, self.label)
}
}
impl<'p> From<Parts<'p>> for Auth<'p> {
fn from(parts: Parts<'p>) -> Self {
Self::from_parts(parts)
}
}
impl<'a> From<Auth<'a>> for Parts<'a> {
fn from(auth: Auth<'a>) -> Self {
auth.into_parts()
}
}
errors! {
Type = Error,
Hack = $,
parse_error => parse(error, string => to_owned),
scheme_error => scheme(error, string => to_owned),
type_of_error => type_of(error, string => to_owned),
label_error => label(error, string => to_owned),
otp_error => otp(error, string => to_owned),
}
impl Auth<'_> {
pub fn base_url(&self) -> Url {
url::base(self.otp().type_of(), self.label())
}
pub fn build_url(&self) -> Url {
let mut url = self.base_url();
self.query_for(&mut url);
url
}
pub fn query_for(&self, url: &mut Url) {
self.otp().query_for(url);
self.label().query_for(url);
}
pub fn parse_url<S: AsRef<str>>(string: S) -> Result<Self, Error> {
fn parse_url_inner(string: &str) -> Result<OwnedParts, Error> {
let url = auth::url::parse(string).map_err(|error| parse_error!(error, string))?;
auth::scheme::check_url(&url).map_err(|error| scheme_error!(error, string))?;
let type_of =
Type::extract_from(&url).map_err(|error| type_of_error!(error, string))?;
let mut query: Query<'_> = url.query_pairs().collect();
let label = Label::extract_from(&mut query, &url)
.map_err(|error| label_error!(error, string))?;
let otp = Otp::extract_from(&mut query, type_of)
.map_err(|error| otp_error!(error, string))?;
Ok((otp, label))
}
parse_url_inner(string.as_ref()).map(Self::from_parts)
}
}
pub type Owned = Auth<'static>;
impl Auth<'_> {
pub fn into_owned(self) -> Owned {
Owned::builder()
.otp(self.otp.into_owned())
.label(self.label.into_owned())
.build()
}
}