oxirs_core/stability/
compatibility.rs1#[derive(Debug, Clone)]
8pub struct VersionEntry {
9 pub version: &'static str,
11 pub release_date: &'static str,
13 pub lts: bool,
15 pub supported_until: Option<&'static str>,
17 pub breaking_changes: Vec<&'static str>,
19 pub deprecated_in: Vec<&'static str>,
21}
22
23impl VersionEntry {
24 pub fn is_supported(&self) -> bool {
29 match self.supported_until {
30 None => true,
31 Some(eol) => eol >= "2026-02-24",
32 }
33 }
34
35 pub fn breaking_change_count(&self) -> usize {
37 self.breaking_changes.len()
38 }
39
40 pub fn deprecation_count(&self) -> usize {
42 self.deprecated_in.len()
43 }
44}
45
46pub struct LtsPolicy;
48
49impl LtsPolicy {
50 pub const SECURITY_PATCH_MONTHS: u32 = 24;
52
53 pub const BUG_FIX_PATCH_MONTHS: u32 = 12;
55
56 pub const BREAKING_CHANGES_IN_MAJOR_ONLY: bool = true;
58
59 pub const SECURITY_BACKPORT_TO_LTS: bool = true;
61
62 pub const MIN_DEPRECATION_NOTICE_VERSIONS: u32 = 1;
64}
65
66#[derive(Debug)]
68pub struct CompatibilityMatrix {
69 pub versions: Vec<VersionEntry>,
71}
72
73impl CompatibilityMatrix {
74 pub fn new() -> Self {
76 Self {
77 versions: Vec::new(),
78 }
79 }
80
81 pub fn oxirs_history() -> Self {
83 let mut matrix = Self::new();
84
85 matrix.versions.push(VersionEntry {
86 version: "0.1.0",
87 release_date: "2025-12-01",
88 lts: false,
89 supported_until: Some("2026-03-01"),
90 breaking_changes: vec![],
91 deprecated_in: vec![],
92 });
93
94 matrix.versions.push(VersionEntry {
95 version: "0.1.1",
96 release_date: "2025-12-15",
97 lts: false,
98 supported_until: Some("2026-03-01"),
99 breaking_changes: vec![],
100 deprecated_in: vec![],
101 });
102
103 matrix.versions.push(VersionEntry {
104 version: "0.1.2",
105 release_date: "2026-01-05",
106 lts: false,
107 supported_until: Some("2026-03-01"),
108 breaking_changes: vec![],
109 deprecated_in: vec![],
110 });
111
112 matrix.versions.push(VersionEntry {
113 version: "0.2.0",
114 release_date: "2026-02-24",
115 lts: false,
116 supported_until: Some("2026-06-01"),
117 breaking_changes: vec![
118 "oxirs-star: Renamed Iri to NamedNode for consistency with RDF 1.2 spec",
119 "oxirs-vec: Removed persistence.rs in favour of modular persistence/ directory",
120 "oxirs-wasm: Removed monolithic parser.rs, store.rs in favour of sub-modules",
121 "oxirs-stream: Removed state.rs; state management moved to state/ sub-module",
122 ],
123 deprecated_in: vec!["RdfStore::legacy_query() - use RdfStore::query() instead"],
124 });
125
126 matrix.versions.push(VersionEntry {
127 version: "0.3.0",
128 release_date: "2026-04-01",
129 lts: false,
130 supported_until: Some("2026-09-01"),
131 breaking_changes: vec![],
132 deprecated_in: vec![],
133 });
134
135 matrix.versions.push(VersionEntry {
136 version: "1.0.0",
137 release_date: "2026-06-01",
138 lts: true,
139 supported_until: Some("2028-06-01"),
140 breaking_changes: vec![
141 "Minimum Rust edition raised to 2024",
142 "All previously Deprecated APIs removed from public surface",
143 "WASM API now requires explicit initialisation via oxirs_wasm::init()",
144 "StabilityLevel enum is now #[non_exhaustive] to allow future variants",
145 ],
146 deprecated_in: vec![],
147 });
148
149 matrix
150 }
151
152 pub fn lts_releases(&self) -> Vec<&VersionEntry> {
154 self.versions.iter().filter(|v| v.lts).collect()
155 }
156
157 pub fn supported_releases(&self) -> Vec<&VersionEntry> {
159 self.versions.iter().filter(|v| v.is_supported()).collect()
160 }
161
162 pub fn latest(&self) -> Option<&VersionEntry> {
164 self.versions.last()
165 }
166
167 pub fn find_version(&self, version: &str) -> Option<&VersionEntry> {
169 self.versions.iter().find(|v| v.version == version)
170 }
171
172 pub fn releases_with_breaking_changes(&self) -> Vec<&VersionEntry> {
174 self.versions
175 .iter()
176 .filter(|v| !v.breaking_changes.is_empty())
177 .collect()
178 }
179
180 pub fn releases_with_deprecations(&self) -> Vec<&VersionEntry> {
182 self.versions
183 .iter()
184 .filter(|v| !v.deprecated_in.is_empty())
185 .collect()
186 }
187
188 pub fn generate_report(&self) -> String {
190 let mut report = String::with_capacity(4096);
191 report.push_str("# OxiRS Version Compatibility Matrix\n\n");
192 report.push_str("## LTS Policy\n\n");
193 report.push_str(&format!(
194 "- Security patches backported to LTS: {}\n",
195 LtsPolicy::SECURITY_BACKPORT_TO_LTS
196 ));
197 report.push_str(&format!(
198 "- Security patch support window: {} months\n",
199 LtsPolicy::SECURITY_PATCH_MONTHS
200 ));
201 report.push_str(&format!(
202 "- Bug-fix support window: {} months\n",
203 LtsPolicy::BUG_FIX_PATCH_MONTHS
204 ));
205 report.push_str(&format!(
206 "- Breaking changes in major versions only: {}\n",
207 LtsPolicy::BREAKING_CHANGES_IN_MAJOR_ONLY
208 ));
209 report.push_str(&format!(
210 "- Minimum deprecation notice: {} minor version(s)\n\n",
211 LtsPolicy::MIN_DEPRECATION_NOTICE_VERSIONS
212 ));
213
214 report.push_str("## Release History\n\n");
215 report.push_str("| Version | Release Date | LTS | Supported Until | Breaking Changes | Deprecations |\n");
216 report.push_str(
217 "|---------|-------------|-----|----------------|-----------------|-------------|\n",
218 );
219 for v in &self.versions {
220 let lts_flag = if v.lts { "YES" } else { "No" };
221 let eol = v.supported_until.unwrap_or("TBD");
222 report.push_str(&format!(
223 "| {} | {} | {} | {} | {} | {} |\n",
224 v.version,
225 v.release_date,
226 lts_flag,
227 eol,
228 v.breaking_changes.len(),
229 v.deprecated_in.len()
230 ));
231 }
232 report.push('\n');
233
234 for v in &self.versions {
236 if !v.breaking_changes.is_empty() {
237 report.push_str(&format!("### Breaking changes in {}\n\n", v.version));
238 for change in &v.breaking_changes {
239 report.push_str(&format!("- {change}\n"));
240 }
241 report.push('\n');
242 }
243 }
244
245 report
246 }
247}
248
249impl Default for CompatibilityMatrix {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 fn matrix() -> CompatibilityMatrix {
260 CompatibilityMatrix::oxirs_history()
261 }
262
263 #[test]
264 fn test_matrix_is_non_empty() {
265 assert!(!matrix().versions.is_empty());
266 }
267
268 #[test]
269 fn test_matrix_has_v1_lts() {
270 let m = matrix();
271 let lts = m.lts_releases();
272 assert!(!lts.is_empty(), "Should have at least one LTS release");
273 assert!(lts.iter().any(|v| v.version == "1.0.0"));
274 }
275
276 #[test]
277 fn test_v1_is_lts() {
278 let m = matrix();
279 let v1 = m.find_version("1.0.0");
280 assert!(v1.is_some());
281 assert!(v1.expect("version should be registered").lts);
282 }
283
284 #[test]
285 fn test_v1_supported_until_set() {
286 let m = matrix();
287 let v1 = m.find_version("1.0.0").expect("operation should succeed");
288 assert!(v1.supported_until.is_some());
289 }
290
291 #[test]
292 fn test_v1_supported_until_two_years() {
293 let m = matrix();
294 let v1 = m.find_version("1.0.0").expect("operation should succeed");
295 let eol = v1.supported_until.expect("supported_until should be set");
296 assert!(
297 eol >= "2028-01-01",
298 "v1.0.0 LTS should be supported at least until 2028"
299 );
300 }
301
302 #[test]
303 fn test_v010_exists() {
304 assert!(matrix().find_version("0.1.0").is_some());
305 }
306
307 #[test]
308 fn test_v020_exists() {
309 assert!(matrix().find_version("0.2.0").is_some());
310 }
311
312 #[test]
313 fn test_v010_is_not_lts() {
314 let m = matrix();
315 let v = m.find_version("0.1.0").expect("operation should succeed");
316 assert!(!v.lts);
317 }
318
319 #[test]
320 fn test_v020_has_breaking_changes() {
321 let m = matrix();
322 let v = m.find_version("0.2.0").expect("operation should succeed");
323 assert!(!v.breaking_changes.is_empty());
324 }
325
326 #[test]
327 fn test_v010_has_no_breaking_changes() {
328 let m = matrix();
329 let v = m.find_version("0.1.0").expect("operation should succeed");
330 assert!(v.breaking_changes.is_empty());
331 }
332
333 #[test]
334 fn test_lts_policy_constants() {
335 assert_eq!(LtsPolicy::SECURITY_PATCH_MONTHS, 24);
336 assert_eq!(LtsPolicy::BUG_FIX_PATCH_MONTHS, 12);
337 const _: () = assert!(LtsPolicy::BREAKING_CHANGES_IN_MAJOR_ONLY);
338 const _: () = assert!(LtsPolicy::SECURITY_BACKPORT_TO_LTS);
339 }
340
341 #[test]
342 fn test_latest_is_v100() {
343 let m = matrix();
344 let latest = m.latest().expect("operation should succeed");
345 assert_eq!(latest.version, "1.0.0");
346 }
347
348 #[test]
349 fn test_releases_with_breaking_changes_non_empty() {
350 let m = matrix();
351 assert!(!m.releases_with_breaking_changes().is_empty());
352 }
353
354 #[test]
355 fn test_releases_with_deprecations_non_empty() {
356 let m = matrix();
357 assert!(!m.releases_with_deprecations().is_empty());
358 }
359
360 #[test]
361 fn test_supported_releases_non_empty() {
362 let m = matrix();
363 assert!(!m.supported_releases().is_empty());
364 }
365
366 #[test]
367 fn test_find_nonexistent_version_returns_none() {
368 let m = matrix();
369 assert!(m.find_version("99.99.99").is_none());
370 }
371
372 #[test]
373 fn test_version_entry_breaking_change_count() {
374 let m = matrix();
375 let v = m.find_version("0.2.0").expect("operation should succeed");
376 assert_eq!(v.breaking_change_count(), v.breaking_changes.len());
377 }
378
379 #[test]
380 fn test_version_entry_deprecation_count() {
381 let m = matrix();
382 let v = m.find_version("0.2.0").expect("operation should succeed");
383 assert_eq!(v.deprecation_count(), v.deprecated_in.len());
384 }
385
386 #[test]
387 fn test_generate_report_non_empty() {
388 assert!(!matrix().generate_report().is_empty());
389 }
390
391 #[test]
392 fn test_generate_report_contains_lts_policy() {
393 let report = matrix().generate_report();
394 assert!(report.contains("LTS Policy"));
395 }
396
397 #[test]
398 fn test_generate_report_contains_release_history() {
399 let report = matrix().generate_report();
400 assert!(report.contains("Release History"));
401 }
402
403 #[test]
404 fn test_generate_report_contains_v1() {
405 let report = matrix().generate_report();
406 assert!(report.contains("1.0.0"));
407 }
408
409 #[test]
410 fn test_default_matrix_is_empty() {
411 let m = CompatibilityMatrix::default();
412 assert!(m.versions.is_empty());
413 }
414}