otp_std/auth/
part.rs

1//! Authentication parts.
2
3use std::{borrow::Cow, fmt, str::FromStr};
4
5use const_macros::const_early;
6
7use miette::Diagnostic;
8
9#[cfg(feature = "serde")]
10use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
11
12use thiserror::Error;
13
14use crate::{
15    auth::{url, utf8},
16    macros::errors,
17};
18
19/// The separator used to join parts.
20pub const SEPARATOR: &str = ":";
21
22/// Represents authentication parts.
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct Part<'p> {
25    string: Cow<'p, str>,
26}
27
28#[cfg(feature = "serde")]
29impl Serialize for Part<'_> {
30    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
31        self.as_str().serialize(serializer)
32    }
33}
34
35#[cfg(feature = "serde")]
36impl<'de> Deserialize<'de> for Part<'_> {
37    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
38        let string = Cow::deserialize(deserializer)?;
39
40        Self::new(string).map_err(de::Error::custom)
41    }
42}
43
44/// Represents errors returned when the part is empty.
45#[derive(Debug, Error, Diagnostic)]
46#[error("the part is empty")]
47#[diagnostic(
48    code(otp_std::auth::part::empty),
49    help("make sure the part is not empty")
50)]
51pub struct EmptyError;
52
53/// Represents errors returned when parts contain the [`SEPARATOR`].
54#[derive(Debug, Error, Diagnostic)]
55#[error("unexpected `{SEPARATOR}` in `{string}`")]
56#[diagnostic(
57    code(otp_std::auth::part::separator),
58    help("make sure the part does not contain `{SEPARATOR}`")
59)]
60pub struct SeparatorError {
61    /// The string that contains the separator.
62    pub string: String,
63}
64
65impl SeparatorError {
66    /// Constructs [`Self`].
67    pub const fn new(string: String) -> Self {
68        Self { string }
69    }
70}
71
72/// Represents sources of errors that can occur when parsing parts.
73#[derive(Debug, Error, Diagnostic)]
74#[error(transparent)]
75#[diagnostic(transparent)]
76pub enum ErrorSource {
77    /// The part is empty.
78    Empty(#[from] EmptyError),
79    /// The part contains the separator.
80    Separator(#[from] SeparatorError),
81}
82
83/// Represents errors that can occur when parsing parts.
84#[derive(Debug, Error, Diagnostic)]
85#[error("failed to parse part")]
86#[diagnostic(code(otp_std::auth::part), help("see the report for more information"))]
87pub struct Error {
88    /// The source of this error.
89    #[source]
90    #[diagnostic_source]
91    pub source: ErrorSource,
92}
93
94impl Error {
95    /// Constructs [`Self`].
96    pub const fn new(source: ErrorSource) -> Self {
97        Self { source }
98    }
99
100    /// Constructs [`Self`] from [`EmptyError`].
101    pub fn empty(error: EmptyError) -> Self {
102        Self::new(error.into())
103    }
104
105    /// Constructs [`Self`] from [`SeparatorError`].
106    pub fn separator(error: SeparatorError) -> Self {
107        Self::new(error.into())
108    }
109
110    /// Constructs [`EmptyError`] and constructs [`Self`] from it.
111    pub fn new_empty() -> Self {
112        Self::empty(EmptyError)
113    }
114
115    /// Constructs [`SeparatorError`] and constructs [`Self`] from it.
116    pub fn new_separator(string: String) -> Self {
117        Self::separator(SeparatorError::new(string))
118    }
119}
120
121impl AsRef<str> for Part<'_> {
122    fn as_ref(&self) -> &str {
123        self.as_str()
124    }
125}
126
127impl Part<'_> {
128    /// Returns the borrowed string.
129    pub fn as_str(&self) -> &str {
130        self.string.as_ref()
131    }
132}
133
134impl fmt::Display for Part<'_> {
135    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136        self.as_str().fmt(formatter)
137    }
138}
139
140impl FromStr for Part<'_> {
141    type Err = Error;
142
143    fn from_str(string: &str) -> Result<Self, Self::Err> {
144        Self::check(string)?;
145
146        // SAFETY: the string was checked to be valid for `Self`
147        Ok(unsafe { Self::owned_unchecked(string.to_owned()) })
148    }
149}
150
151errors! {
152    Type = Error,
153    Hack = $,
154    empty_error => new_empty(),
155    separator_error => new_separator(string => to_owned),
156}
157
158impl<'p> Part<'p> {
159    /// Constructs [`Self`], if possible.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`struct@Error`] if the given string is empty or contains the [`SEPARATOR`].
164    pub fn new(string: Cow<'p, str>) -> Result<Self, Error> {
165        Self::check(string.as_ref())?;
166
167        // SAFETY: the string was checked to be valid for `Self`
168        Ok(unsafe { Self::new_unchecked(string) })
169    }
170
171    /// Checks whether the given string is valid for constructing [`Self`].
172    ///
173    /// # Errors
174    ///
175    /// Returns [`struct@Error`] if the given string is empty or contains the [`SEPARATOR`].
176    pub fn check<S: AsRef<str>>(string: S) -> Result<(), Error> {
177        fn check_inner(string: &str) -> Result<(), Error> {
178            const_early!(string.is_empty() => empty_error!());
179
180            const_early!(string.contains(SEPARATOR) => separator_error!(string));
181
182            Ok(())
183        }
184
185        check_inner(string.as_ref())
186    }
187
188    /// Constructs [`Self`] without checking the given string.
189    ///
190    /// # Safety
191    ///
192    /// The given string must be non-empty and must not contain the [`SEPARATOR`].
193    pub const unsafe fn new_unchecked(string: Cow<'p, str>) -> Self {
194        Self { string }
195    }
196
197    /// Constructs [`Self`] from owned data, if possible.
198    ///
199    /// # Errors
200    ///
201    /// Returns [`struct@Error`] if the given string is empty or contains the [`SEPARATOR`].
202    pub fn owned(string: String) -> Result<Self, Error> {
203        Self::new(Cow::Owned(string))
204    }
205
206    /// Constructs [`Self`] from owned data without checking the given string.
207    ///
208    /// # Safety
209    ///
210    /// The given string must be non-empty and must not contain the [`SEPARATOR`].
211    pub const unsafe fn owned_unchecked(string: String) -> Self {
212        // SAFETY: the caller must ensure the string is valid
213        unsafe { Self::new_unchecked(Cow::Owned(string)) }
214    }
215
216    /// Constructs [`Self`] from borrowed data, if possible.
217    ///
218    /// # Errors
219    ///
220    /// Returns [`struct@Error`] if the given string is empty or contains the [`SEPARATOR`].
221    pub fn borrowed(string: &'p str) -> Result<Self, Error> {
222        Self::new(Cow::Borrowed(string))
223    }
224
225    /// Constructs [`Self`] from borrowed data without checking the given string.
226    ///
227    /// # Safety
228    ///
229    /// The given string must be non-empty and must not contain the [`SEPARATOR`].
230    pub const unsafe fn borrowed_unchecked(string: &'p str) -> Self {
231        // SAFETY: the caller must ensure the string is valid
232        unsafe { Self::new_unchecked(Cow::Borrowed(string)) }
233    }
234
235    /// Consumes [`Self`] and returns the contained string.
236    pub fn get(self) -> Cow<'p, str> {
237        self.string
238    }
239}
240
241/// Represents sources of errors that can occur when decoding parts.
242#[derive(Debug, Error, Diagnostic)]
243#[error(transparent)]
244#[diagnostic(transparent)]
245pub enum DecodeErrorSource {
246    /// The part contains invalid UTF-8.
247    Utf8(#[from] utf8::Error),
248    /// The part is empty or contains the separator.
249    Part(#[from] Error),
250}
251
252/// Represents errors that can occur when decoding parts.
253#[derive(Debug, Error, Diagnostic)]
254#[error("failed to decode part")]
255#[diagnostic(
256    code(otp_std::auth::part::decode),
257    help("see the report for more information")
258)]
259pub struct DecodeError {
260    /// The source of this error.
261    #[source]
262    #[diagnostic_source]
263    pub source: DecodeErrorSource,
264}
265
266impl DecodeError {
267    /// Constructs [`Self`].
268    pub const fn new(source: DecodeErrorSource) -> Self {
269        Self { source }
270    }
271
272    /// Constructs [`Self`] from [`utf8::Error`].
273    pub fn utf8(error: utf8::Error) -> Self {
274        Self::new(error.into())
275    }
276
277    /// Constructs [`Self`] from [`struct@Error`].
278    pub fn part(error: Error) -> Self {
279        Self::new(error.into())
280    }
281}
282
283impl Part<'_> {
284    /// Decodes the given string.
285    ///
286    /// # Errors
287    ///
288    /// Returns [`DecodeError`] if the given string could not be decoded.
289    pub fn decode<S: AsRef<str>>(string: S) -> Result<Self, DecodeError> {
290        let decoded = url::decode(string.as_ref())
291            .map_err(utf8::wrap)
292            .map_err(DecodeError::utf8)?;
293
294        Self::owned(decoded.into_owned()).map_err(DecodeError::part)
295    }
296}
297
298impl Part<'_> {
299    /// Encodes the contained string.
300    pub fn encode(&self) -> Cow<'_, str> {
301        url::encode(self.as_str())
302    }
303}
304
305/// Represents owned [`Part`].
306pub type Owned = Part<'static>;
307
308impl Part<'_> {
309    /// Converts [`Self`] into [`Owned`].
310    pub fn into_owned(self) -> Owned {
311        // SAFETY: the contained string is valid (by construction)
312        unsafe { Owned::owned_unchecked(self.get().into_owned()) }
313    }
314}