#[cfg(feature = "alloc")]
extern crate alloc;
use core::error::Error as CoreError;
use core::fmt::{Display, Error as FmtError, Formatter};
use core::num::ParseIntError;
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned as _, string::String};
#[cfg(feature = "facet-unstable")]
use facet::Facet;
#[derive(Debug)]
#[non_exhaustive]
#[expect(clippy::error_impl_error, reason = "common enough convention")]
pub enum Error<'data> {
BuildNoPrefix,
NoPrefix(&'data str, &'data str),
NoSuffix(&'data str, &'data str),
NoVDot(&'data str),
TwoComponentsExpected(&'data str),
UIntExpected(&'data str, &'data str, ParseIntError),
}
impl Display for Error<'_> {
#[inline]
#[expect(clippy::min_ident_chars, reason = "this is the way it is defined")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match *self {
Self::BuildNoPrefix => write!(
f,
"No prefix specified for the media-type-version config builder"
),
Self::NoPrefix(value, prefix) => {
write!(
f,
"The '{value}' media type does not have the expected prefix '{prefix}'"
)
}
Self::NoSuffix(value, suffix) => {
write!(
f,
"The '{value}' media type does not have the expected suffix '{suffix}'"
)
}
Self::NoVDot(value) => write!(
f,
"The '{value}' media type does not have the expected '.v' part"
),
Self::TwoComponentsExpected(value) => write!(
f,
"The '{value}' media type does not have two dot-separated version components"
),
Self::UIntExpected(value, comp, _) => write!(
f,
"The '{value}' media type contains an invalid unsigned integer '{comp}'"
),
}
}
}
impl CoreError for Error<'_> {
#[inline]
fn source(&self) -> Option<&(dyn CoreError + 'static)> {
match *self {
Self::BuildNoPrefix
| Self::NoPrefix(_, _)
| Self::NoSuffix(_, _)
| Self::NoVDot(_)
| Self::TwoComponentsExpected(_) => None,
Self::UIntExpected(_, _, ref err) => Some(err),
}
}
}
#[cfg(feature = "alloc")]
impl Error<'_> {
#[inline]
#[must_use]
pub fn into_owned_error(self) -> OwnedError {
match self {
Self::BuildNoPrefix => OwnedError::BuildNoPrefix,
Self::NoPrefix(value, prefix) => {
OwnedError::NoPrefix(value.to_owned(), prefix.to_owned())
}
Self::NoSuffix(value, suffix) => {
OwnedError::NoSuffix(value.to_owned(), suffix.to_owned())
}
Self::NoVDot(value) => OwnedError::NoVDot(value.to_owned()),
Self::TwoComponentsExpected(value) => {
OwnedError::TwoComponentsExpected(value.to_owned())
}
Self::UIntExpected(value, comp, err) => {
OwnedError::UIntExpected(value.to_owned(), comp.to_owned(), err)
}
}
}
}
#[cfg(feature = "alloc")]
#[derive(Debug)]
#[non_exhaustive]
pub enum OwnedError {
BuildNoPrefix,
NoPrefix(String, String),
NoSuffix(String, String),
NoVDot(String),
TwoComponentsExpected(String),
UIntExpected(String, String, ParseIntError),
}
#[cfg(feature = "alloc")]
impl Display for OwnedError {
#[inline]
#[expect(clippy::min_ident_chars, reason = "this is the way it is defined")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match *self {
Self::BuildNoPrefix => Error::BuildNoPrefix.fmt(f),
Self::NoPrefix(ref value, ref prefix) => Error::NoPrefix(value, prefix).fmt(f),
Self::NoSuffix(ref value, ref suffix) => Error::NoSuffix(value, suffix).fmt(f),
Self::NoVDot(ref value) => Error::NoVDot(value).fmt(f),
Self::TwoComponentsExpected(ref value) => Error::TwoComponentsExpected(value).fmt(f),
Self::UIntExpected(ref value, ref comp, ref err) => {
Error::UIntExpected(value, comp, (*err).clone()).fmt(f)
}
}
}
}
#[cfg(feature = "alloc")]
impl CoreError for OwnedError {
#[inline]
fn source(&self) -> Option<&(dyn CoreError + 'static)> {
match *self {
Self::BuildNoPrefix
| Self::NoPrefix(_, _)
| Self::NoSuffix(_, _)
| Self::NoVDot(_)
| Self::TwoComponentsExpected(_) => None,
Self::UIntExpected(_, _, ref err) => Some(err),
}
}
}
#[cfg_attr(feature = "facet-unstable", derive(Facet))]
pub struct Version {
major: u32,
minor: u32,
}
impl Version {
#[inline]
#[must_use]
pub const fn major(&self) -> u32 {
self.major
}
#[inline]
#[must_use]
pub const fn minor(&self) -> u32 {
self.minor
}
#[inline]
#[must_use]
pub const fn as_tuple(&self) -> (u32, u32) {
(self.major, self.minor)
}
}
impl From<(u32, u32)> for Version {
#[inline]
fn from(value: (u32, u32)) -> Self {
Self {
major: value.0,
minor: value.1,
}
}
}
impl From<Version> for (u32, u32) {
#[inline]
fn from(value: Version) -> Self {
value.as_tuple()
}
}
#[cfg_attr(feature = "facet-unstable", derive(Facet))]
pub struct Config<'data> {
prefix: &'data str,
suffix: &'data str,
}
impl<'data> Config<'data> {
#[inline]
#[must_use]
pub const fn prefix(&self) -> &str {
self.prefix
}
#[inline]
#[must_use]
pub const fn suffix(&self) -> &str {
self.suffix
}
#[inline]
#[must_use]
pub fn builder() -> ConfigBuilder<'data> {
ConfigBuilder::default()
}
#[cfg(test)]
#[inline]
#[must_use]
pub const fn from_parts(prefix: &'data str, suffix: &'data str) -> Self {
Self { prefix, suffix }
}
}
#[derive(Default)]
pub struct ConfigBuilder<'data> {
prefix: Option<&'data str>,
suffix: Option<&'data str>,
}
impl<'data> ConfigBuilder<'data> {
#[inline]
#[must_use]
pub const fn prefix(self, value: &'data str) -> Self {
Self {
prefix: Some(value),
..self
}
}
#[inline]
#[must_use]
pub const fn suffix(self, value: &'data str) -> Self {
Self {
suffix: Some(value),
..self
}
}
#[inline]
pub fn build(self) -> Result<Config<'data>, Error<'data>> {
Ok(Config {
prefix: self.prefix.ok_or(Error::BuildNoPrefix)?,
suffix: self.suffix.unwrap_or_default(),
})
}
}
#[cfg(test)]
#[expect(clippy::panic_in_result_fn, reason = "this is a test suite")]
#[expect(clippy::unwrap_used, reason = "this is a test suite")]
mod tests {
extern crate alloc;
use alloc::format;
use alloc::string::String;
#[cfg(feature = "facet-unstable")]
use alloc::string::ToString as _;
#[cfg(feature = "alloc")]
use core::str::FromStr as _;
use eyre::WrapErr as _;
use facet_testhelpers::test;
use log::{info, trace};
#[cfg(feature = "facet-unstable")]
use facet_pretty::FacetPretty as _;
use super::Config;
#[cfg(feature = "alloc")]
use super::Error;
#[cfg(feature = "facet-unstable")]
use super::Version;
#[cfg(feature = "facet-unstable")]
fn pretty_cfg(cfg: &Config<'_>) -> String {
format!("{cfg}", cfg = cfg.pretty())
}
#[cfg(not(feature = "facet-unstable"))]
fn pretty_cfg(cfg: &Config<'_>) -> String {
format!(
"Config {{ prefix = {prefix:?}, suffix = {suffix:?} }}",
prefix = cfg.prefix(),
suffix = cfg.suffix()
)
}
#[test]
fn builder() {
info!("Building a config builder");
let cfg = Config::builder()
.prefix("hello")
.suffix("goodbye")
.build()
.context("build")?;
trace!("{cfg}", cfg = pretty_cfg(&cfg));
assert_eq!(cfg.prefix(), "hello");
assert_eq!(cfg.suffix(), "goodbye");
}
#[cfg(feature = "alloc")]
#[test]
fn error_to_owned() {
let check_to_owned_msg = |err: Error<'_>| {
let msg = format!("{err}");
trace!("{msg}");
let owned = err.into_owned_error();
let owned_msg = format!("{owned}");
trace!("{owned_msg}");
assert_eq!(msg, owned_msg);
};
check_to_owned_msg(Error::BuildNoPrefix);
check_to_owned_msg(Error::NoPrefix("some value", "some prefix"));
check_to_owned_msg(Error::NoSuffix("some value", "some suffix"));
check_to_owned_msg(Error::NoVDot("stuff"));
check_to_owned_msg(Error::TwoComponentsExpected("some kind of thing"));
check_to_owned_msg(Error::UIntExpected(
"something",
"something else",
u32::from_str("?").unwrap_err(),
));
}
#[cfg(feature = "facet-unstable")]
#[test]
fn facet_pretty_contains_things() {
let major = 42;
let minor = 616;
let ver = Version::from((major, minor));
let repr = format!("{ver}", ver = ver.pretty());
assert!(
repr.contains("/// The major version number"),
"no docstring in the pretty representation: {repr:?}"
);
assert!(
repr.contains(&major.to_string()),
"no '{major}' in the pretty representation: {repr:?}"
);
assert!(
repr.contains(&minor.to_string()),
"no '{minor}' in the pretty representation: {repr:?}"
);
}
}