use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ApiStability {
Stable,
Experimental,
BackendSpecific,
MigrationOnly,
}
impl ApiStability {
pub const fn label(self) -> &'static str {
match self {
Self::Stable => "stable",
Self::Experimental => "experimental",
Self::BackendSpecific => "backend-specific",
Self::MigrationOnly => "migration-only",
}
}
pub const fn is_semver_protected(self) -> bool {
matches!(self, Self::Stable)
}
pub const fn may_change_without_major(self) -> bool {
!self.is_semver_protected()
}
}
pub trait ApiStabilityMarker: Copy + Clone + Default + Eq + PartialEq {
const STABILITY: ApiStability;
fn stability(self) -> ApiStability {
Self::STABILITY
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Stable;
impl ApiStabilityMarker for Stable {
const STABILITY: ApiStability = ApiStability::Stable;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Experimental;
impl ApiStabilityMarker for Experimental {
const STABILITY: ApiStability = ApiStability::Experimental;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct BackendSpecific;
impl ApiStabilityMarker for BackendSpecific {
const STABILITY: ApiStability = ApiStability::BackendSpecific;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct MigrationOnly;
impl ApiStabilityMarker for MigrationOnly {
const STABILITY: ApiStability = ApiStability::MigrationOnly;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StabilityNote {
pub stability: ApiStability,
pub since: Option<&'static str>,
pub note: &'static str,
}
impl StabilityNote {
pub const fn new(
stability: ApiStability,
since: Option<&'static str>,
note: &'static str,
) -> Self {
Self {
stability,
since,
note,
}
}
pub const fn stable(since: &'static str, note: &'static str) -> Self {
Self::new(ApiStability::Stable, Some(since), note)
}
pub const fn experimental(since: &'static str, note: &'static str) -> Self {
Self::new(ApiStability::Experimental, Some(since), note)
}
pub const fn backend_specific(feature: &'static str) -> Self {
Self::new(ApiStability::BackendSpecific, None, feature)
}
pub const fn migration_only(note: &'static str) -> Self {
Self::new(ApiStability::MigrationOnly, None, note)
}
pub const fn is_semver_protected(self) -> bool {
self.stability.is_semver_protected()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FeatureStability {
pub feature: &'static str,
pub stability: ApiStability,
pub since: Option<&'static str>,
pub note: &'static str,
}
impl FeatureStability {
pub const fn new(
feature: &'static str,
stability: ApiStability,
since: Option<&'static str>,
note: &'static str,
) -> Self {
Self {
feature,
stability,
since,
note,
}
}
pub const fn stable(feature: &'static str, since: &'static str, note: &'static str) -> Self {
Self::new(feature, ApiStability::Stable, Some(since), note)
}
pub const fn experimental(
feature: &'static str,
since: &'static str,
note: &'static str,
) -> Self {
Self::new(feature, ApiStability::Experimental, Some(since), note)
}
pub const fn backend_specific(feature: &'static str, note: &'static str) -> Self {
Self::new(feature, ApiStability::BackendSpecific, None, note)
}
pub const fn migration_only(feature: &'static str, note: &'static str) -> Self {
Self::new(feature, ApiStability::MigrationOnly, None, note)
}
pub const fn is_semver_protected(self) -> bool {
self.stability.is_semver_protected()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ApiStatus<S: ApiStabilityMarker> {
pub since: Option<&'static str>,
pub note: &'static str,
marker: PhantomData<S>,
}
impl<S: ApiStabilityMarker> ApiStatus<S> {
pub const fn new(since: Option<&'static str>, note: &'static str) -> Self {
Self {
since,
note,
marker: PhantomData,
}
}
pub const fn stability(&self) -> ApiStability {
S::STABILITY
}
pub const fn note(&self) -> StabilityNote {
StabilityNote::new(S::STABILITY, self.since, self.note)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn marker_types_classify_api_stability() {
assert_eq!(Stable.stability(), ApiStability::Stable);
assert_eq!(Experimental.stability(), ApiStability::Experimental);
assert_eq!(BackendSpecific.stability(), ApiStability::BackendSpecific);
assert_eq!(MigrationOnly.stability(), ApiStability::MigrationOnly);
}
#[test]
fn stability_notes_encode_semver_expectations() {
let stable = StabilityNote::stable("5.0.0", "Public layout primitives");
let experimental = StabilityNote::experimental("5.0.0", "Early host runtime policy");
assert!(stable.is_semver_protected());
assert!(experimental.stability.may_change_without_major());
assert_eq!(ApiStability::MigrationOnly.label(), "migration-only");
}
#[test]
fn feature_stability_records_feature_scope() {
let wgpu =
FeatureStability::backend_specific("wgpu", "Renderer availability depends on backend");
let status = ApiStatus::<Stable>::new(Some("5.0.0"), "Core document tree");
assert_eq!(wgpu.feature, "wgpu");
assert!(!wgpu.is_semver_protected());
assert_eq!(status.stability(), ApiStability::Stable);
assert_eq!(status.note().since, Some("5.0.0"));
}
#[test]
fn v8_api_stability_doc_covers_release_gate_families() {
let docs = include_str!("../../docs/v8_0_api_stability.md");
for required in [
"Stable Product APIs",
"Experimental Diagnostics And Devtools",
"Testing And Replay Helpers",
"Backend-Specific APIs",
"Migration-Only Surfaces",
"CommandRegistry",
"AnimationMachine",
"CanvasHostCaptureDiagnosticReport",
"InteractionRecorder",
"ScenarioHarness",
"VirtualizationDiagnostics",
"layout_animation_transitions",
"Dock workspace",
"domain-neutral dock panel drag/drop targets",
"floating-panel state",
"accessibility_debug_overlay",
"ThemePatchExport",
] {
assert!(
docs.contains(required),
"v8 API stability guide should document `{required}`"
);
}
}
#[test]
fn v8_completion_audit_tracks_partial_and_done_release_gates() {
let docs = include_str!("../../docs/v8_0_completion_audit.md");
for required in [
"Roadmap Themes",
"Release Gates",
"Animation state-machine inspector",
"Virtualized tree and table 2.0",
"Layout animations",
"Docking workspace",
"Showcase examples for each major feature",
"No tests embedded in showcase source",
"Package docs distinguish stable APIs from diagnostics",
"Next Work",
"Partial",
"Done",
] {
assert!(
docs.contains(required),
"v8 completion audit should document `{required}`"
);
}
}
}