#[cfg(any(feature = "alloc", feature = "toml-boml1"))]
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(all(feature = "alloc", feature = "toml-boml1"))]
use alloc::format;
#[cfg(feature = "facet-unstable")]
use facet::Facet;
#[cfg(feature = "toml-boml1")]
use boml::TomlError;
#[derive(Debug)]
#[non_exhaustive]
#[expect(clippy::error_impl_error, reason = "common enough convention")]
pub enum Error<'data> {
BuildNoPrefix,
Internal(u32),
NoPrefix(&'data str, &'data str),
NoSuffix(&'data str, &'data str),
NoVDot(&'data str),
#[cfg(feature = "extract-from-table")]
TableNoChild(&'data str),
#[cfg(feature = "extract-from-table")]
TableNotTable,
#[cfg(feature = "toml-boml1")]
TomlParse(TomlError<'data>),
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::Internal(code) => write!(f, "media-type-version internal error: code {code}"),
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"
),
#[cfg(feature = "extract-from-table")]
Self::TableNoChild(comp) => {
write!(f, "The parsed structure did not contain the '{comp}' child")
}
#[cfg(feature = "extract-from-table")]
Self::TableNotTable => write!(
f,
"The parsed structure did not contain an expected table or string"
),
#[cfg(feature = "toml-boml1")]
Self::TomlParse(ref err) => write!(f, "Could not parse a TOML document: {err}"),
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::Internal(_)
| Self::NoPrefix(_, _)
| Self::NoSuffix(_, _)
| Self::NoVDot(_)
| Self::TwoComponentsExpected(_) => None,
#[cfg(feature = "extract-from-table")]
Self::TableNoChild(_) | Self::TableNotTable => None,
#[cfg(feature = "toml-boml1")]
Self::TomlParse(_) => 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::Internal(code) => OwnedError::Internal(code),
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()),
#[cfg(feature = "extract-from-table")]
Self::TableNoChild(comp) => OwnedError::TableNoChild(comp.to_owned()),
#[cfg(feature = "extract-from-table")]
Self::TableNotTable => OwnedError::TableNotTable,
#[cfg(feature = "toml-boml1")]
Self::TomlParse(err) => OwnedError::TomlBoml(format!("{err}")),
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,
Internal(u32),
NoPrefix(String, String),
NoSuffix(String, String),
NoVDot(String),
#[cfg(feature = "extract-from-table")]
TableNoChild(String),
#[cfg(feature = "extract-from-table")]
TableNotTable,
#[cfg(feature = "toml-boml1")]
TomlBoml(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::Internal(ref code) => Error::Internal(*code).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),
#[cfg(feature = "extract-from-table")]
Self::TableNoChild(ref comp) => Error::TableNoChild(comp).fmt(f),
#[cfg(feature = "extract-from-table")]
Self::TableNotTable => Error::TableNotTable.fmt(f),
#[cfg(feature = "toml-boml1")]
Self::TomlBoml(ref err) => write!(f, "Could not parse a TOML document: {err}"),
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::Internal(_)
| Self::NoPrefix(_, _)
| Self::NoSuffix(_, _)
| Self::NoVDot(_)
| Self::TwoComponentsExpected(_) => None,
#[cfg(feature = "extract-from-table")]
Self::TableNoChild(_) | Self::TableNotTable => None,
#[cfg(feature = "toml-boml1")]
Self::TomlBoml(_) => 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::{Result, 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() -> Result<()> {
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");
Ok(())
}
#[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:?}"
);
}
}