1#[cfg(any(feature = "alloc", feature = "toml-boml1"))]
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(all(feature = "alloc", feature = "toml-boml1"))]
16use alloc::format;
17
18#[cfg(any(feature = "facet030-unstable", feature = "facet032-unstable"))]
19use facet::Facet;
20
21#[cfg(feature = "toml-boml1")]
22use boml::TomlError;
23
24#[derive(Debug)]
26#[non_exhaustive]
27#[expect(clippy::error_impl_error, reason = "common enough convention")]
28pub enum Error<'data> {
29 BuildNoPrefix,
31
32 Internal(u32),
34
35 NoPrefix(&'data str, &'data str),
37
38 NoSuffix(&'data str, &'data str),
40
41 NoVDot(&'data str),
43
44 #[cfg(feature = "extract-from-table")]
45 TableNoChild(&'data str),
47
48 #[cfg(feature = "extract-from-table")]
49 TableNotTable,
51
52 #[cfg(feature = "toml-boml1")]
53 TomlParse(TomlError<'data>),
55
56 TwoComponentsExpected(&'data str),
58
59 UIntExpected(&'data str, &'data str, ParseIntError),
61}
62
63impl Display for Error<'_> {
64 #[inline]
66 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
67 match *self {
68 Self::BuildNoPrefix => write!(
69 f,
70 "No prefix specified for the media-type-version config builder"
71 ),
72 Self::Internal(code) => write!(f, "media-type-version internal error: code {code}"),
73 Self::NoPrefix(value, prefix) => {
74 write!(
75 f,
76 "The '{value}' media type does not have the expected prefix '{prefix}'"
77 )
78 }
79 Self::NoSuffix(value, suffix) => {
80 write!(
81 f,
82 "The '{value}' media type does not have the expected suffix '{suffix}'"
83 )
84 }
85 Self::NoVDot(value) => write!(
86 f,
87 "The '{value}' media type does not have the expected '.v' part"
88 ),
89 Self::TwoComponentsExpected(value) => write!(
90 f,
91 "The '{value}' media type does not have two dot-separated version components"
92 ),
93 #[cfg(feature = "extract-from-table")]
94 Self::TableNoChild(comp) => {
95 write!(f, "The parsed structure did not contain the '{comp}' child")
96 }
97 #[cfg(feature = "extract-from-table")]
98 Self::TableNotTable => write!(
99 f,
100 "The parsed structure did not contain an expected table or string"
101 ),
102 #[cfg(feature = "toml-boml1")]
103 Self::TomlParse(ref err) => write!(f, "Could not parse a TOML document: {err}"),
104 Self::UIntExpected(value, comp, _) => write!(
105 f,
106 "The '{value}' media type contains an invalid unsigned integer '{comp}'"
107 ),
108 }
109 }
110}
111
112impl CoreError for Error<'_> {
113 #[inline]
114 fn source(&self) -> Option<&(dyn CoreError + 'static)> {
115 match *self {
116 Self::BuildNoPrefix
117 | Self::Internal(_)
118 | Self::NoPrefix(_, _)
119 | Self::NoSuffix(_, _)
120 | Self::NoVDot(_)
121 | Self::TwoComponentsExpected(_) => None,
122 #[cfg(feature = "extract-from-table")]
123 Self::TableNoChild(_) | Self::TableNotTable => None,
124 #[cfg(feature = "toml-boml1")]
125 Self::TomlParse(_) => None,
126 Self::UIntExpected(_, _, ref err) => Some(err),
127 }
128 }
129}
130
131#[cfg(feature = "alloc")]
132impl Error<'_> {
133 #[inline]
135 #[must_use]
136 pub fn into_owned_error(self) -> OwnedError {
137 match self {
138 Self::BuildNoPrefix => OwnedError::BuildNoPrefix,
139 Self::Internal(code) => OwnedError::Internal(code),
140 Self::NoPrefix(value, prefix) => {
141 OwnedError::NoPrefix(value.to_owned(), prefix.to_owned())
142 }
143 Self::NoSuffix(value, suffix) => {
144 OwnedError::NoSuffix(value.to_owned(), suffix.to_owned())
145 }
146 Self::NoVDot(value) => OwnedError::NoVDot(value.to_owned()),
147 #[cfg(feature = "extract-from-table")]
148 Self::TableNoChild(comp) => OwnedError::TableNoChild(comp.to_owned()),
149 #[cfg(feature = "extract-from-table")]
150 Self::TableNotTable => OwnedError::TableNotTable,
151 #[cfg(feature = "toml-boml1")]
152 Self::TomlParse(err) => OwnedError::TomlBoml(format!("{err}")),
153 Self::TwoComponentsExpected(value) => {
154 OwnedError::TwoComponentsExpected(value.to_owned())
155 }
156 Self::UIntExpected(value, comp, err) => {
157 OwnedError::UIntExpected(value.to_owned(), comp.to_owned(), err)
158 }
159 }
160 }
161}
162
163#[cfg(feature = "alloc")]
165#[derive(Debug)]
166#[non_exhaustive]
167pub enum OwnedError {
168 BuildNoPrefix,
170
171 Internal(u32),
173
174 NoPrefix(String, String),
176
177 NoSuffix(String, String),
179
180 NoVDot(String),
182
183 #[cfg(feature = "extract-from-table")]
184 TableNoChild(String),
186
187 #[cfg(feature = "extract-from-table")]
188 TableNotTable,
190
191 #[cfg(feature = "toml-boml1")]
192 TomlBoml(String),
194
195 TwoComponentsExpected(String),
197
198 UIntExpected(String, String, ParseIntError),
200}
201
202#[cfg(feature = "alloc")]
203impl Display for OwnedError {
204 #[inline]
206 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
207 match *self {
208 Self::BuildNoPrefix => Error::BuildNoPrefix.fmt(f),
209 Self::Internal(ref code) => Error::Internal(*code).fmt(f),
210 Self::NoPrefix(ref value, ref prefix) => Error::NoPrefix(value, prefix).fmt(f),
211 Self::NoSuffix(ref value, ref suffix) => Error::NoSuffix(value, suffix).fmt(f),
212 Self::NoVDot(ref value) => Error::NoVDot(value).fmt(f),
213 #[cfg(feature = "extract-from-table")]
214 Self::TableNoChild(ref comp) => Error::TableNoChild(comp).fmt(f),
215 #[cfg(feature = "extract-from-table")]
216 Self::TableNotTable => Error::TableNotTable.fmt(f),
217 #[cfg(feature = "toml-boml1")]
218 Self::TomlBoml(ref err) => write!(f, "Could not parse a TOML document: {err}"),
219 Self::TwoComponentsExpected(ref value) => Error::TwoComponentsExpected(value).fmt(f),
220 Self::UIntExpected(ref value, ref comp, ref err) => {
221 Error::UIntExpected(value, comp, (*err).clone()).fmt(f)
222 }
223 }
224 }
225}
226
227#[cfg(feature = "alloc")]
228impl CoreError for OwnedError {
229 #[inline]
230 fn source(&self) -> Option<&(dyn CoreError + 'static)> {
231 match *self {
232 Self::BuildNoPrefix
233 | Self::Internal(_)
234 | Self::NoPrefix(_, _)
235 | Self::NoSuffix(_, _)
236 | Self::NoVDot(_)
237 | Self::TwoComponentsExpected(_) => None,
238 #[cfg(feature = "extract-from-table")]
239 Self::TableNoChild(_) | Self::TableNotTable => None,
240 #[cfg(feature = "toml-boml1")]
241 Self::TomlBoml(_) => None,
242 Self::UIntExpected(_, _, ref err) => Some(err),
243 }
244 }
245}
246
247#[cfg_attr(
249 any(feature = "facet030-unstable", feature = "facet032-unstable"),
250 derive(Facet)
251)]
252pub struct Version {
253 major: u32,
255
256 minor: u32,
258}
259
260impl Version {
261 #[inline]
263 #[must_use]
264 pub const fn major(&self) -> u32 {
265 self.major
266 }
267
268 #[inline]
270 #[must_use]
271 pub const fn minor(&self) -> u32 {
272 self.minor
273 }
274
275 #[inline]
277 #[must_use]
278 pub const fn as_tuple(&self) -> (u32, u32) {
279 (self.major, self.minor)
280 }
281}
282
283impl From<(u32, u32)> for Version {
284 #[inline]
286 fn from(value: (u32, u32)) -> Self {
287 Self {
288 major: value.0,
289 minor: value.1,
290 }
291 }
292}
293
294impl From<Version> for (u32, u32) {
295 #[inline]
297 fn from(value: Version) -> Self {
298 value.as_tuple()
299 }
300}
301
302#[cfg_attr(
304 any(feature = "facet030-unstable", feature = "facet032-unstable"),
305 derive(Facet)
306)]
307pub struct Config<'data> {
308 prefix: &'data str,
310
311 suffix: &'data str,
313}
314
315impl<'data> Config<'data> {
316 #[inline]
318 #[must_use]
319 pub const fn prefix(&self) -> &str {
320 self.prefix
321 }
322
323 #[inline]
325 #[must_use]
326 pub const fn suffix(&self) -> &str {
327 self.suffix
328 }
329
330 #[inline]
332 #[must_use]
333 pub fn builder() -> ConfigBuilder<'data> {
334 ConfigBuilder::default()
335 }
336
337 #[cfg(test)]
339 #[inline]
340 #[must_use]
341 pub const fn from_parts(prefix: &'data str, suffix: &'data str) -> Self {
342 Self { prefix, suffix }
343 }
344}
345
346#[derive(Default)]
348pub struct ConfigBuilder<'data> {
349 prefix: Option<&'data str>,
351
352 suffix: Option<&'data str>,
354}
355
356impl<'data> ConfigBuilder<'data> {
357 #[inline]
359 #[must_use]
360 pub const fn prefix(self, value: &'data str) -> Self {
361 Self {
362 prefix: Some(value),
363 ..self
364 }
365 }
366
367 #[inline]
369 #[must_use]
370 pub const fn suffix(self, value: &'data str) -> Self {
371 Self {
372 suffix: Some(value),
373 ..self
374 }
375 }
376
377 #[inline]
383 pub fn build(self) -> Result<Config<'data>, Error<'data>> {
384 Ok(Config {
385 prefix: self.prefix.ok_or(Error::BuildNoPrefix)?,
386 suffix: self.suffix.unwrap_or_default(),
387 })
388 }
389}
390
391#[cfg(test)]
392#[expect(clippy::unwrap_used, reason = "this is a test suite")]
393mod tests {
394 extern crate alloc;
395
396 use alloc::format;
397 use alloc::string::String;
398
399 #[cfg(any(feature = "facet030-unstable", feature = "facet032-unstable"))]
400 use alloc::string::ToString as _;
401
402 #[cfg(feature = "alloc")]
403 use core::str::FromStr as _;
404
405 use eyre::{Result, WrapErr as _};
406 use facet_testhelpers::test;
407 use log::{info, trace};
408
409 #[cfg(feature = "facet030-unstable")]
410 use facet_pretty030::FacetPretty as _;
411
412 #[cfg(feature = "facet032-unstable")]
413 use facet_pretty032::FacetPretty as _;
414
415 use super::Config;
416
417 #[cfg(feature = "alloc")]
418 use super::Error;
419
420 #[cfg(any(feature = "facet030-unstable", feature = "facet032-unstable"))]
421 use super::Version;
422
423 #[cfg(any(feature = "facet030-unstable", feature = "facet032-unstable"))]
424 fn pretty_cfg(cfg: &Config<'_>) -> String {
425 format!("{cfg}", cfg = cfg.pretty())
426 }
427
428 #[cfg(not(any(feature = "facet030-unstable", feature = "facet032-unstable")))]
429 fn pretty_cfg(cfg: &Config<'_>) -> String {
430 format!(
431 "Config {{ prefix = {prefix:?}, suffix = {suffix:?} }}",
432 prefix = cfg.prefix(),
433 suffix = cfg.suffix()
434 )
435 }
436
437 #[test]
439 fn builder() -> Result<()> {
440 info!("Building a config builder");
441 let cfg = Config::builder()
442 .prefix("hello")
443 .suffix("goodbye")
444 .build()
445 .context("build")?;
446 trace!("{cfg}", cfg = pretty_cfg(&cfg));
447 assert_eq!(cfg.prefix(), "hello");
448 assert_eq!(cfg.suffix(), "goodbye");
449 Ok(())
450 }
451
452 #[cfg(feature = "alloc")]
454 #[test]
455 fn error_to_owned() {
456 let check_to_owned_msg = |err: Error<'_>| {
457 let msg = format!("{err}");
458 trace!("{msg}");
459 let owned = err.into_owned_error();
460 let owned_msg = format!("{owned}");
461 trace!("{owned_msg}");
462 assert_eq!(msg, owned_msg);
463 };
464
465 check_to_owned_msg(Error::BuildNoPrefix);
466 check_to_owned_msg(Error::NoPrefix("some value", "some prefix"));
467 check_to_owned_msg(Error::NoSuffix("some value", "some suffix"));
468 check_to_owned_msg(Error::NoVDot("stuff"));
469 check_to_owned_msg(Error::TwoComponentsExpected("some kind of thing"));
470 check_to_owned_msg(Error::UIntExpected(
471 "something",
472 "something else",
473 u32::from_str("?").unwrap_err(),
474 ));
475 }
476
477 #[cfg(any(feature = "facet030-unstable", feature = "facet032-unstable"))]
479 #[test]
480 fn facet_pretty_contains_things() {
481 let major = 42;
482 let minor = 616;
483 let ver = Version::from((major, minor));
484 let repr = format!("{ver}", ver = ver.pretty());
485 assert!(
486 repr.contains("Version"),
487 "no Version in the pretty representation: {repr:?}"
488 );
489 assert!(
490 repr.contains(&major.to_string()),
491 "no '{major}' in the pretty representation: {repr:?}"
492 );
493 assert!(
494 repr.contains(&minor.to_string()),
495 "no '{minor}' in the pretty representation: {repr:?}"
496 );
497 assert!(
498 repr.contains('\n'),
499 "no newline in the pretty representation: {repr:?}"
500 );
501 }
502}