#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stable;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unstable;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Experimental;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Deprecated;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StabilityLevel {
Stable,
Unstable,
Experimental,
Deprecated,
}
impl StabilityLevel {
pub fn is_production_ready(self) -> bool {
matches!(self, Self::Stable)
}
pub fn may_change(self) -> bool {
matches!(self, Self::Unstable | Self::Experimental)
}
pub fn is_deprecated(self) -> bool {
matches!(self, Self::Deprecated)
}
pub fn badge_label(self) -> &'static str {
match self {
Self::Stable => "stability: stable",
Self::Unstable => "stability: unstable",
Self::Experimental => "stability: experimental",
Self::Deprecated => "stability: deprecated",
}
}
}
impl std::fmt::Display for StabilityLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = match self {
Self::Stable => "stable",
Self::Unstable => "unstable",
Self::Experimental => "experimental",
Self::Deprecated => "deprecated",
};
f.write_str(label)
}
}
impl std::str::FromStr for StabilityLevel {
type Err = StabilityParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"stable" => Ok(Self::Stable),
"unstable" => Ok(Self::Unstable),
"experimental" => Ok(Self::Experimental),
"deprecated" => Ok(Self::Deprecated),
_ => Err(StabilityParseError {
input: s.to_owned(),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StabilityParseError {
pub input: String,
}
impl std::fmt::Display for StabilityParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"unknown stability level '{}': expected one of stable, unstable, experimental, deprecated",
self.input
)
}
}
impl std::error::Error for StabilityParseError {}
pub trait HasStability {
fn stability() -> StabilityLevel;
fn is_production_ready() -> bool {
Self::stability().is_production_ready()
}
fn may_change() -> bool {
Self::stability().may_change()
}
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn msrv() -> &'static str {
"nightly (edition 2024)"
}
pub fn stability_policy_summary() -> &'static str {
"OxiPhysics follows semver for Stable APIs. \
Unstable APIs may change in minor versions. \
Experimental APIs may change at any time. \
Deprecated APIs will be removed in a future major version."
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_stable() {
assert_eq!(StabilityLevel::Stable.to_string(), "stable");
}
#[test]
fn display_unstable() {
assert_eq!(StabilityLevel::Unstable.to_string(), "unstable");
}
#[test]
fn display_experimental() {
assert_eq!(StabilityLevel::Experimental.to_string(), "experimental");
}
#[test]
fn display_deprecated() {
assert_eq!(StabilityLevel::Deprecated.to_string(), "deprecated");
}
#[test]
fn parse_round_trip() {
for level in [
StabilityLevel::Stable,
StabilityLevel::Unstable,
StabilityLevel::Experimental,
StabilityLevel::Deprecated,
] {
let parsed: StabilityLevel = level.to_string().parse().expect("round-trip parse");
assert_eq!(parsed, level);
}
}
#[test]
fn parse_case_insensitive() {
assert_eq!(
"STABLE".parse::<StabilityLevel>().expect("upper case"),
StabilityLevel::Stable
);
assert_eq!(
"Experimental"
.parse::<StabilityLevel>()
.expect("mixed case"),
StabilityLevel::Experimental
);
}
#[test]
fn parse_invalid() {
let err = "bogus".parse::<StabilityLevel>().expect_err("should fail");
assert_eq!(err.input, "bogus");
assert!(err.to_string().contains("bogus"));
}
#[test]
fn production_ready() {
assert!(StabilityLevel::Stable.is_production_ready());
assert!(!StabilityLevel::Unstable.is_production_ready());
assert!(!StabilityLevel::Experimental.is_production_ready());
assert!(!StabilityLevel::Deprecated.is_production_ready());
}
#[test]
fn may_change() {
assert!(!StabilityLevel::Stable.may_change());
assert!(StabilityLevel::Unstable.may_change());
assert!(StabilityLevel::Experimental.may_change());
assert!(!StabilityLevel::Deprecated.may_change());
}
#[test]
fn is_deprecated() {
assert!(StabilityLevel::Deprecated.is_deprecated());
assert!(!StabilityLevel::Stable.is_deprecated());
}
#[test]
fn badge_labels() {
assert_eq!(StabilityLevel::Stable.badge_label(), "stability: stable");
assert_eq!(
StabilityLevel::Experimental.badge_label(),
"stability: experimental"
);
}
#[test]
fn version_not_empty() {
assert!(!version().is_empty());
}
#[test]
fn msrv_contains_nightly() {
assert!(msrv().contains("nightly"));
}
#[test]
fn policy_summary_not_empty() {
assert!(!stability_policy_summary().is_empty());
}
#[test]
fn has_stability_trait() {
struct TestApi;
impl HasStability for TestApi {
fn stability() -> StabilityLevel {
StabilityLevel::Unstable
}
}
assert_eq!(TestApi::stability(), StabilityLevel::Unstable);
assert!(!TestApi::is_production_ready());
assert!(TestApi::may_change());
}
#[test]
fn marker_structs_are_eq() {
assert_eq!(Stable, Stable);
assert_eq!(Unstable, Unstable);
assert_eq!(Experimental, Experimental);
assert_eq!(Deprecated, Deprecated);
}
}