1use 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
19pub const SEPARATOR: &str = ":";
21
22#[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#[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#[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 pub string: String,
63}
64
65impl SeparatorError {
66 pub const fn new(string: String) -> Self {
68 Self { string }
69 }
70}
71
72#[derive(Debug, Error, Diagnostic)]
74#[error(transparent)]
75#[diagnostic(transparent)]
76pub enum ErrorSource {
77 Empty(#[from] EmptyError),
79 Separator(#[from] SeparatorError),
81}
82
83#[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 #[source]
90 #[diagnostic_source]
91 pub source: ErrorSource,
92}
93
94impl Error {
95 pub const fn new(source: ErrorSource) -> Self {
97 Self { source }
98 }
99
100 pub fn empty(error: EmptyError) -> Self {
102 Self::new(error.into())
103 }
104
105 pub fn separator(error: SeparatorError) -> Self {
107 Self::new(error.into())
108 }
109
110 pub fn new_empty() -> Self {
112 Self::empty(EmptyError)
113 }
114
115 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 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 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 pub fn new(string: Cow<'p, str>) -> Result<Self, Error> {
165 Self::check(string.as_ref())?;
166
167 Ok(unsafe { Self::new_unchecked(string) })
169 }
170
171 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 pub const unsafe fn new_unchecked(string: Cow<'p, str>) -> Self {
194 Self { string }
195 }
196
197 pub fn owned(string: String) -> Result<Self, Error> {
203 Self::new(Cow::Owned(string))
204 }
205
206 pub const unsafe fn owned_unchecked(string: String) -> Self {
212 unsafe { Self::new_unchecked(Cow::Owned(string)) }
214 }
215
216 pub fn borrowed(string: &'p str) -> Result<Self, Error> {
222 Self::new(Cow::Borrowed(string))
223 }
224
225 pub const unsafe fn borrowed_unchecked(string: &'p str) -> Self {
231 unsafe { Self::new_unchecked(Cow::Borrowed(string)) }
233 }
234
235 pub fn get(self) -> Cow<'p, str> {
237 self.string
238 }
239}
240
241#[derive(Debug, Error, Diagnostic)]
243#[error(transparent)]
244#[diagnostic(transparent)]
245pub enum DecodeErrorSource {
246 Utf8(#[from] utf8::Error),
248 Part(#[from] Error),
250}
251
252#[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 #[source]
262 #[diagnostic_source]
263 pub source: DecodeErrorSource,
264}
265
266impl DecodeError {
267 pub const fn new(source: DecodeErrorSource) -> Self {
269 Self { source }
270 }
271
272 pub fn utf8(error: utf8::Error) -> Self {
274 Self::new(error.into())
275 }
276
277 pub fn part(error: Error) -> Self {
279 Self::new(error.into())
280 }
281}
282
283impl Part<'_> {
284 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 pub fn encode(&self) -> Cow<'_, str> {
301 url::encode(self.as_str())
302 }
303}
304
305pub type Owned = Part<'static>;
307
308impl Part<'_> {
309 pub fn into_owned(self) -> Owned {
311 unsafe { Owned::owned_unchecked(self.get().into_owned()) }
313 }
314}