oxiphysics_core/
stability.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct Stable;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct Unstable;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct Experimental;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct Deprecated;
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum StabilityLevel {
70 Stable,
72 Unstable,
74 Experimental,
76 Deprecated,
78}
79
80impl StabilityLevel {
81 pub fn is_production_ready(self) -> bool {
85 matches!(self, Self::Stable)
86 }
87
88 pub fn may_change(self) -> bool {
90 matches!(self, Self::Unstable | Self::Experimental)
91 }
92
93 pub fn is_deprecated(self) -> bool {
95 matches!(self, Self::Deprecated)
96 }
97
98 pub fn badge_label(self) -> &'static str {
100 match self {
101 Self::Stable => "stability: stable",
102 Self::Unstable => "stability: unstable",
103 Self::Experimental => "stability: experimental",
104 Self::Deprecated => "stability: deprecated",
105 }
106 }
107}
108
109impl std::fmt::Display for StabilityLevel {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 let label = match self {
112 Self::Stable => "stable",
113 Self::Unstable => "unstable",
114 Self::Experimental => "experimental",
115 Self::Deprecated => "deprecated",
116 };
117 f.write_str(label)
118 }
119}
120
121impl std::str::FromStr for StabilityLevel {
122 type Err = StabilityParseError;
123
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 match s.to_ascii_lowercase().as_str() {
126 "stable" => Ok(Self::Stable),
127 "unstable" => Ok(Self::Unstable),
128 "experimental" => Ok(Self::Experimental),
129 "deprecated" => Ok(Self::Deprecated),
130 _ => Err(StabilityParseError {
131 input: s.to_owned(),
132 }),
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct StabilityParseError {
140 pub input: String,
142}
143
144impl std::fmt::Display for StabilityParseError {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 write!(
147 f,
148 "unknown stability level '{}': expected one of stable, unstable, experimental, deprecated",
149 self.input
150 )
151 }
152}
153
154impl std::error::Error for StabilityParseError {}
155
156pub trait HasStability {
173 fn stability() -> StabilityLevel;
175
176 fn is_production_ready() -> bool {
178 Self::stability().is_production_ready()
179 }
180
181 fn may_change() -> bool {
183 Self::stability().may_change()
184 }
185}
186
187pub fn version() -> &'static str {
189 env!("CARGO_PKG_VERSION")
190}
191
192pub fn msrv() -> &'static str {
194 "nightly (edition 2024)"
195}
196
197pub fn stability_policy_summary() -> &'static str {
199 "OxiPhysics follows semver for Stable APIs. \
200 Unstable APIs may change in minor versions. \
201 Experimental APIs may change at any time. \
202 Deprecated APIs will be removed in a future major version."
203}
204
205#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn display_stable() {
214 assert_eq!(StabilityLevel::Stable.to_string(), "stable");
215 }
216
217 #[test]
218 fn display_unstable() {
219 assert_eq!(StabilityLevel::Unstable.to_string(), "unstable");
220 }
221
222 #[test]
223 fn display_experimental() {
224 assert_eq!(StabilityLevel::Experimental.to_string(), "experimental");
225 }
226
227 #[test]
228 fn display_deprecated() {
229 assert_eq!(StabilityLevel::Deprecated.to_string(), "deprecated");
230 }
231
232 #[test]
233 fn parse_round_trip() {
234 for level in [
235 StabilityLevel::Stable,
236 StabilityLevel::Unstable,
237 StabilityLevel::Experimental,
238 StabilityLevel::Deprecated,
239 ] {
240 let parsed: StabilityLevel = level.to_string().parse().expect("round-trip parse");
241 assert_eq!(parsed, level);
242 }
243 }
244
245 #[test]
246 fn parse_case_insensitive() {
247 assert_eq!(
248 "STABLE".parse::<StabilityLevel>().expect("upper case"),
249 StabilityLevel::Stable
250 );
251 assert_eq!(
252 "Experimental"
253 .parse::<StabilityLevel>()
254 .expect("mixed case"),
255 StabilityLevel::Experimental
256 );
257 }
258
259 #[test]
260 fn parse_invalid() {
261 let err = "bogus".parse::<StabilityLevel>().expect_err("should fail");
262 assert_eq!(err.input, "bogus");
263 assert!(err.to_string().contains("bogus"));
264 }
265
266 #[test]
267 fn production_ready() {
268 assert!(StabilityLevel::Stable.is_production_ready());
269 assert!(!StabilityLevel::Unstable.is_production_ready());
270 assert!(!StabilityLevel::Experimental.is_production_ready());
271 assert!(!StabilityLevel::Deprecated.is_production_ready());
272 }
273
274 #[test]
275 fn may_change() {
276 assert!(!StabilityLevel::Stable.may_change());
277 assert!(StabilityLevel::Unstable.may_change());
278 assert!(StabilityLevel::Experimental.may_change());
279 assert!(!StabilityLevel::Deprecated.may_change());
280 }
281
282 #[test]
283 fn is_deprecated() {
284 assert!(StabilityLevel::Deprecated.is_deprecated());
285 assert!(!StabilityLevel::Stable.is_deprecated());
286 }
287
288 #[test]
289 fn badge_labels() {
290 assert_eq!(StabilityLevel::Stable.badge_label(), "stability: stable");
291 assert_eq!(
292 StabilityLevel::Experimental.badge_label(),
293 "stability: experimental"
294 );
295 }
296
297 #[test]
298 fn version_not_empty() {
299 assert!(!version().is_empty());
300 }
301
302 #[test]
303 fn msrv_contains_nightly() {
304 assert!(msrv().contains("nightly"));
305 }
306
307 #[test]
308 fn policy_summary_not_empty() {
309 assert!(!stability_policy_summary().is_empty());
310 }
311
312 #[test]
313 fn has_stability_trait() {
314 struct TestApi;
315 impl HasStability for TestApi {
316 fn stability() -> StabilityLevel {
317 StabilityLevel::Unstable
318 }
319 }
320 assert_eq!(TestApi::stability(), StabilityLevel::Unstable);
321 assert!(!TestApi::is_production_ready());
322 assert!(TestApi::may_change());
323 }
324
325 #[test]
326 fn marker_structs_are_eq() {
327 assert_eq!(Stable, Stable);
328 assert_eq!(Unstable, Unstable);
329 assert_eq!(Experimental, Experimental);
330 assert_eq!(Deprecated, Deprecated);
331 }
332}