1use bon::Builder;
4use miette::Diagnostic;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use thiserror::Error;
10
11use crate::{
12 auth::{
13 self,
14 label::{self, Label},
15 query::Query,
16 scheme,
17 url::{self, Url},
18 },
19 macros::errors,
20 otp::{
21 self,
22 core::Otp,
23 type_of::{self, Type},
24 },
25};
26
27pub const SCHEME: &str = "otpauth";
29
30pub const BASE_URL_ALWAYS_VALID: &str = "OTP base URL is always valid";
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Builder)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36pub struct Auth<'a> {
37 #[builder(into)]
39 pub otp: Otp<'a>,
40 pub label: Label<'a>,
42}
43
44#[derive(Debug, Error, Diagnostic)]
46#[error(transparent)]
47#[diagnostic(transparent)]
48pub enum ErrorSource {
49 Url(#[from] url::Error),
51 Scheme(#[from] scheme::Error),
53 TypeOf(#[from] type_of::Error),
55 Label(#[from] label::Error),
57 Otp(#[from] otp::core::Error),
59}
60
61#[derive(Debug, Error, Diagnostic)]
63#[error("failed to extract auth from `{string}`")]
64#[diagnostic(code(otp_std::auth::core), help("see the report for more information"))]
65pub struct Error {
66 #[source]
68 #[diagnostic_source]
69 pub source: ErrorSource,
70 pub string: String,
72}
73
74impl Error {
75 pub const fn new(source: ErrorSource, string: String) -> Self {
77 Self { source, string }
78 }
79
80 pub fn parse(error: url::Error, string: String) -> Self {
82 Self::new(error.into(), string)
83 }
84
85 pub fn scheme(error: scheme::Error, string: String) -> Self {
87 Self::new(error.into(), string)
88 }
89
90 pub fn type_of(error: type_of::Error, string: String) -> Self {
92 Self::new(error.into(), string)
93 }
94
95 pub fn label(error: label::Error, string: String) -> Self {
97 Self::new(error.into(), string)
98 }
99
100 pub fn otp(error: otp::core::Error, string: String) -> Self {
102 Self::new(error.into(), string)
103 }
104}
105
106impl Auth<'_> {
107 pub const fn otp(&self) -> &Otp<'_> {
109 &self.otp
110 }
111
112 pub const fn label(&self) -> &Label<'_> {
114 &self.label
115 }
116}
117
118pub type Parts<'p> = (Otp<'p>, Label<'p>);
120
121pub type OwnedParts = Parts<'static>;
123
124impl<'a> Auth<'a> {
125 pub fn from_parts(parts: Parts<'a>) -> Self {
127 let (otp, label) = parts;
128
129 Self::builder().otp(otp).label(label).build()
130 }
131
132 pub fn into_parts(self) -> Parts<'a> {
134 (self.otp, self.label)
135 }
136}
137
138impl<'p> From<Parts<'p>> for Auth<'p> {
139 fn from(parts: Parts<'p>) -> Self {
140 Self::from_parts(parts)
141 }
142}
143
144impl<'a> From<Auth<'a>> for Parts<'a> {
145 fn from(auth: Auth<'a>) -> Self {
146 auth.into_parts()
147 }
148}
149
150errors! {
151 Type = Error,
152 Hack = $,
153 parse_error => parse(error, string => to_owned),
154 scheme_error => scheme(error, string => to_owned),
155 type_of_error => type_of(error, string => to_owned),
156 label_error => label(error, string => to_owned),
157 otp_error => otp(error, string => to_owned),
158}
159
160impl Auth<'_> {
161 pub fn base_url(&self) -> Url {
167 url::base(self.otp().type_of(), self.label())
168 }
169
170 pub fn build_url(&self) -> Url {
172 let mut url = self.base_url();
173
174 self.query_for(&mut url);
175
176 url
177 }
178
179 pub fn query_for(&self, url: &mut Url) {
181 self.otp().query_for(url);
182 self.label().query_for(url);
183 }
184
185 pub fn parse_url<S: AsRef<str>>(string: S) -> Result<Self, Error> {
191 fn parse_url_inner(string: &str) -> Result<OwnedParts, Error> {
192 let url = auth::url::parse(string).map_err(|error| parse_error!(error, string))?;
193
194 auth::scheme::check_url(&url).map_err(|error| scheme_error!(error, string))?;
195
196 let type_of =
197 Type::extract_from(&url).map_err(|error| type_of_error!(error, string))?;
198
199 let mut query: Query<'_> = url.query_pairs().collect();
200
201 let label = Label::extract_from(&mut query, &url)
202 .map_err(|error| label_error!(error, string))?;
203
204 let otp = Otp::extract_from(&mut query, type_of)
205 .map_err(|error| otp_error!(error, string))?;
206
207 Ok((otp, label))
208 }
209
210 parse_url_inner(string.as_ref()).map(Self::from_parts)
211 }
212}
213
214pub type Owned = Auth<'static>;
216
217impl Auth<'_> {
218 pub fn into_owned(self) -> Owned {
220 Owned::builder()
221 .otp(self.otp.into_owned())
222 .label(self.label.into_owned())
223 .build()
224 }
225}