1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// Copyright 2026 COOLJAPAN OU (Team KitaSan)
// SPDX-License-Identifier: Apache-2.0
//! API stability markers for OxiPhysics.
//!
//! This module provides types and traits to document the stability level
//! of public APIs. Consumers can query stability at runtime via the
//! [`HasStability`] trait, and library authors annotate items with the
//! marker structs ([`Stable`], [`Unstable`], [`Experimental`],
//! [`Deprecated`]) in documentation or associated types.
//!
//! # Stability Policy
//!
//! | Level | Guarantee |
//! |----------------|-----------|
//! | **Stable** | Follows semver strictly. Breaking changes only in major versions. |
//! | **Unstable** | May change in minor versions with a deprecation notice. |
//! | **Experimental** | May change or be removed at any time without notice. |
//! | **Deprecated** | Scheduled for removal in a future version. |
//!
//! # Example
//!
//! ```no_run
//! use oxiphysics_core::stability::{StabilityLevel, HasStability};
//!
//! struct MyApi;
//!
//! impl HasStability for MyApi {
//! fn stability() -> StabilityLevel {
//! StabilityLevel::Stable
//! }
//! }
//!
//! assert_eq!(MyApi::stability(), StabilityLevel::Stable);
//! assert_eq!(MyApi::stability().to_string(), "stable");
//! ```
/// Marker type for stable APIs.
///
/// Stable APIs follow semver strictly: breaking changes happen only
/// across major version boundaries.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stable;
/// Marker type for unstable APIs.
///
/// Unstable APIs may change in minor releases. A deprecation notice
/// will be issued before removal whenever possible.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unstable;
/// Marker type for experimental APIs.
///
/// Experimental APIs may change or be removed at any time. They are
/// provided for early feedback and should not be relied upon in
/// production code.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Experimental;
/// Marker type for deprecated APIs.
///
/// Deprecated APIs are scheduled for removal in a future version.
/// Migration guidance is provided in the item-level documentation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Deprecated;
/// Runtime-queryable API stability level.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StabilityLevel {
/// The API is stable and follows semver.
Stable,
/// The API is unstable and may change in minor versions.
Unstable,
/// The API is experimental and may change or be removed at any time.
Experimental,
/// The API is deprecated and will be removed in a future version.
Deprecated,
}
impl StabilityLevel {
/// Returns `true` if the API is considered safe for production use.
///
/// Only [`StabilityLevel::Stable`] qualifies.
pub fn is_production_ready(self) -> bool {
matches!(self, Self::Stable)
}
/// Returns `true` if the API may change without a major version bump.
pub fn may_change(self) -> bool {
matches!(self, Self::Unstable | Self::Experimental)
}
/// Returns `true` if the API is deprecated.
pub fn is_deprecated(self) -> bool {
matches!(self, Self::Deprecated)
}
/// Returns a human-readable label suitable for documentation badges.
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(),
}),
}
}
}
/// Error returned when parsing an invalid stability level string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StabilityParseError {
/// The invalid input string.
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 {}
/// Trait for types that declare their API stability level.
///
/// Implement this on public API entry-point types so consumers can
/// programmatically query how stable an API is.
///
/// ```no_run
/// use oxiphysics_core::stability::{StabilityLevel, HasStability};
///
/// struct RigidBodySolver;
///
/// impl HasStability for RigidBodySolver {
/// fn stability() -> StabilityLevel {
/// StabilityLevel::Stable
/// }
/// }
/// ```
pub trait HasStability {
/// Returns the stability level of this API.
fn stability() -> StabilityLevel;
/// Returns `true` if this API is production-ready.
fn is_production_ready() -> bool {
Self::stability().is_production_ready()
}
/// Returns `true` if this API may change without a major version bump.
fn may_change() -> bool {
Self::stability().may_change()
}
}
/// Returns the version of the `oxiphysics-core` crate at build time.
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
/// Returns the minimum supported Rust version (MSRV) for OxiPhysics.
pub fn msrv() -> &'static str {
"nightly (edition 2024)"
}
/// Returns a summary of the stability policy as a static string slice.
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."
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[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);
}
}