1#[cfg(feature = "alloc")]
6extern crate alloc;
7
8use core::error::Error as CoreError;
9use core::fmt::{Display, Error as FmtError, Formatter};
10use core::num::ParseIntError;
11
12#[cfg(feature = "alloc")]
13use alloc::{borrow::ToOwned as _, string::String};
14
15#[cfg(feature = "facet-unstable")]
16use facet::Facet;
17
18#[derive(Debug)]
20#[non_exhaustive]
21#[expect(clippy::error_impl_error, reason = "common enough convention")]
22pub enum Error<'data> {
23 BuildNoPrefix,
25
26 NoPrefix(&'data str, &'data str),
28
29 NoSuffix(&'data str, &'data str),
31
32 NoVDot(&'data str),
34
35 TwoComponentsExpected(&'data str),
37
38 UIntExpected(&'data str, &'data str, ParseIntError),
40}
41
42impl Display for Error<'_> {
43 #[inline]
45 #[expect(clippy::min_ident_chars, reason = "this is the way it is defined")]
46 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
47 match *self {
48 Self::BuildNoPrefix => write!(
49 f,
50 "No prefix specified for the media-type-version config builder"
51 ),
52 Self::NoPrefix(value, prefix) => {
53 write!(
54 f,
55 "The '{value}' media type does not have the expected prefix '{prefix}'"
56 )
57 }
58 Self::NoSuffix(value, suffix) => {
59 write!(
60 f,
61 "The '{value}' media type does not have the expected suffix '{suffix}'"
62 )
63 }
64 Self::NoVDot(value) => write!(
65 f,
66 "The '{value}' media type does not have the expected '.v' part"
67 ),
68 Self::TwoComponentsExpected(value) => write!(
69 f,
70 "The '{value}' media type does not have two dot-separated version components"
71 ),
72 Self::UIntExpected(value, comp, ref err) => write!(
73 f,
74 "The '{value}' media type contains an invalid unsigned integer '{comp}': {err}"
75 ),
76 }
77 }
78}
79
80impl CoreError for Error<'_> {}
81
82#[cfg(feature = "alloc")]
83impl Error<'_> {
84 #[inline]
86 #[must_use]
87 pub fn into_owned_error(self) -> OwnedError {
88 match self {
89 Self::BuildNoPrefix => OwnedError::BuildNoPrefix,
90 Self::NoPrefix(value, prefix) => {
91 OwnedError::NoPrefix(value.to_owned(), prefix.to_owned())
92 }
93 Self::NoSuffix(value, suffix) => {
94 OwnedError::NoSuffix(value.to_owned(), suffix.to_owned())
95 }
96 Self::NoVDot(value) => OwnedError::NoVDot(value.to_owned()),
97 Self::TwoComponentsExpected(value) => {
98 OwnedError::TwoComponentsExpected(value.to_owned())
99 }
100 Self::UIntExpected(value, comp, err) => {
101 OwnedError::UIntExpected(value.to_owned(), comp.to_owned(), err)
102 }
103 }
104 }
105}
106
107#[cfg(feature = "alloc")]
109#[derive(Debug)]
110#[non_exhaustive]
111pub enum OwnedError {
112 BuildNoPrefix,
114
115 NoPrefix(String, String),
117
118 NoSuffix(String, String),
120
121 NoVDot(String),
123
124 TwoComponentsExpected(String),
126
127 UIntExpected(String, String, ParseIntError),
129}
130
131#[cfg(feature = "alloc")]
132impl Display for OwnedError {
133 #[inline]
135 #[expect(clippy::min_ident_chars, reason = "this is the way it is defined")]
136 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
137 match *self {
138 Self::BuildNoPrefix => Error::BuildNoPrefix.fmt(f),
139 Self::NoPrefix(ref value, ref prefix) => Error::NoPrefix(value, prefix).fmt(f),
140 Self::NoSuffix(ref value, ref suffix) => Error::NoSuffix(value, suffix).fmt(f),
141 Self::NoVDot(ref value) => Error::NoVDot(value).fmt(f),
142 Self::TwoComponentsExpected(ref value) => Error::TwoComponentsExpected(value).fmt(f),
143 Self::UIntExpected(ref value, ref comp, ref err) => {
144 Error::UIntExpected(value, comp, (*err).clone()).fmt(f)
145 }
146 }
147 }
148}
149
150#[cfg(feature = "alloc")]
151impl CoreError for OwnedError {}
152
153#[cfg_attr(feature = "facet-unstable", derive(Facet))]
155pub struct Version {
156 major: u32,
158
159 minor: u32,
161}
162
163impl Version {
164 #[inline]
166 #[must_use]
167 pub const fn major(&self) -> u32 {
168 self.major
169 }
170
171 #[inline]
173 #[must_use]
174 pub const fn minor(&self) -> u32 {
175 self.minor
176 }
177
178 #[inline]
180 #[must_use]
181 pub const fn as_tuple(&self) -> (u32, u32) {
182 (self.major, self.minor)
183 }
184}
185
186impl From<(u32, u32)> for Version {
187 #[inline]
189 fn from(value: (u32, u32)) -> Self {
190 Self {
191 major: value.0,
192 minor: value.1,
193 }
194 }
195}
196
197impl From<Version> for (u32, u32) {
198 #[inline]
200 fn from(value: Version) -> Self {
201 value.as_tuple()
202 }
203}
204
205#[cfg_attr(feature = "facet-unstable", derive(Facet))]
207pub struct Config<'data> {
208 prefix: &'data str,
210
211 suffix: &'data str,
213}
214
215impl<'data> Config<'data> {
216 #[inline]
218 #[must_use]
219 pub const fn prefix(&self) -> &str {
220 self.prefix
221 }
222
223 #[inline]
225 #[must_use]
226 pub const fn suffix(&self) -> &str {
227 self.suffix
228 }
229
230 #[inline]
232 #[must_use]
233 pub fn builder() -> ConfigBuilder<'data> {
234 ConfigBuilder::default()
235 }
236
237 #[cfg(test)]
239 #[inline]
240 #[must_use]
241 pub const fn from_parts(prefix: &'data str, suffix: &'data str) -> Self {
242 Self { prefix, suffix }
243 }
244}
245
246#[derive(Default)]
248pub struct ConfigBuilder<'data> {
249 prefix: Option<&'data str>,
251
252 suffix: Option<&'data str>,
254}
255
256impl<'data> ConfigBuilder<'data> {
257 #[inline]
259 #[must_use]
260 pub const fn prefix(self, value: &'data str) -> Self {
261 Self {
262 prefix: Some(value),
263 ..self
264 }
265 }
266
267 #[inline]
269 #[must_use]
270 pub const fn suffix(self, value: &'data str) -> Self {
271 Self {
272 suffix: Some(value),
273 ..self
274 }
275 }
276
277 #[inline]
283 pub fn build(self) -> Result<Config<'data>, Error<'data>> {
284 Ok(Config {
285 prefix: self.prefix.ok_or(Error::BuildNoPrefix)?,
286 suffix: self.suffix.unwrap_or_default(),
287 })
288 }
289}
290
291#[cfg(test)]
292#[expect(clippy::panic_in_result_fn, reason = "this is a test suite")]
293#[expect(clippy::unwrap_used, reason = "this is a test suite")]
294mod tests {
295 extern crate alloc;
296
297 use alloc::format;
298 use alloc::string::String;
299
300 #[cfg(feature = "alloc")]
301 use core::str::FromStr as _;
302
303 use eyre::WrapErr as _;
304 use facet_testhelpers::test;
305 use log::{info, trace};
306
307 #[cfg(feature = "facet-unstable")]
308 use facet_pretty::FacetPretty as _;
309
310 use super::Config;
311
312 #[cfg(feature = "alloc")]
313 use super::Error;
314
315 #[cfg(feature = "facet-unstable")]
316 fn pretty_cfg(cfg: &Config<'_>) -> String {
317 format!("{cfg}", cfg = cfg.pretty())
318 }
319
320 #[cfg(not(feature = "facet-unstable"))]
321 fn pretty_cfg(cfg: &Config<'_>) -> String {
322 format!(
323 "Config {{ prefix = {prefix:?}, suffix = {suffix:?} }}",
324 prefix = cfg.prefix(),
325 suffix = cfg.suffix()
326 )
327 }
328
329 #[test]
331 fn builder() {
332 info!("Building a config builder");
333 let cfg = Config::builder()
334 .prefix("hello")
335 .suffix("goodbye")
336 .build()
337 .context("build")?;
338 trace!("{cfg}", cfg = pretty_cfg(&cfg));
339 assert_eq!(cfg.prefix(), "hello");
340 assert_eq!(cfg.suffix(), "goodbye");
341 }
342
343 #[cfg(feature = "alloc")]
345 #[test]
346 fn error_to_owned() {
347 let check_to_owned_msg = |err: Error<'_>| {
348 let msg = format!("{err}");
349 trace!("{msg}");
350 let owned = err.into_owned_error();
351 let owned_msg = format!("{owned}");
352 trace!("{owned_msg}");
353 assert_eq!(msg, owned_msg);
354 };
355
356 check_to_owned_msg(Error::BuildNoPrefix);
357 check_to_owned_msg(Error::NoPrefix("some value", "some prefix"));
358 check_to_owned_msg(Error::NoSuffix("some value", "some suffix"));
359 check_to_owned_msg(Error::NoVDot("stuff"));
360 check_to_owned_msg(Error::TwoComponentsExpected("some kind of thing"));
361 check_to_owned_msg(Error::UIntExpected(
362 "something",
363 "something else",
364 u32::from_str("?").unwrap_err(),
365 ));
366 }
367}