1use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct AbiVersion {
18 major: u32,
19 minor: u32,
20}
21
22impl AbiVersion {
23 pub fn new(major: u32, minor: u32) -> Self {
24 Self { major, minor }
25 }
26
27 pub fn try_parse(s: &str) -> Option<Self> {
28 let parts: Vec<&str> = s.split('.').collect();
29 if parts.len() == 2 {
30 if let (Ok(major), Ok(minor)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
31 return Some(Self { major, minor });
32 }
33 }
34 None
35 }
36
37 pub fn to_string_val(&self) -> String {
38 format!("{}.{}", self.major, self.minor)
39 }
40
41 pub fn is_compatible_with(&self, other: &AbiVersion) -> bool {
42 self.major == other.major
44 }
45
46 pub fn is_backward_compatible(&self, older: &AbiVersion) -> bool {
47 if self.major != older.major {
49 return false;
50 }
51 self.minor >= older.minor
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub struct PlatformArch {
59 pub platform: String,
60 pub architecture: String,
61}
62
63impl PlatformArch {
64 pub fn new(platform: &str, architecture: &str) -> Self {
65 Self {
66 platform: platform.to_string(),
67 architecture: architecture.to_string(),
68 }
69 }
70
71 pub fn as_string(&self) -> String {
72 format!("{}-{}", self.platform, self.architecture)
73 }
74
75 pub fn from_string(s: &str) -> Option<Self> {
76 let parts: Vec<&str> = s.split('-').collect();
77 if parts.len() == 2 {
78 Some(Self::new(parts[0], parts[1]))
79 } else {
80 None
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
87#[serde(rename_all = "PascalCase")]
88pub enum CompatibilityLevel {
89 Incompatible, Deprecated, Compatible, Recommended, }
94
95impl CompatibilityLevel {
96 pub fn as_str(&self) -> &'static str {
97 match self {
98 CompatibilityLevel::Incompatible => "Incompatible",
99 CompatibilityLevel::Deprecated => "Deprecated",
100 CompatibilityLevel::Compatible => "Compatible",
101 CompatibilityLevel::Recommended => "Recommended",
102 }
103 }
104
105 pub fn try_parse(s: &str) -> Option<Self> {
106 match s {
107 "Incompatible" => Some(CompatibilityLevel::Incompatible),
108 "Deprecated" => Some(CompatibilityLevel::Deprecated),
109 "Compatible" => Some(CompatibilityLevel::Compatible),
110 "Recommended" => Some(CompatibilityLevel::Recommended),
111 _ => None,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct BreakingChange {
119 pub from_version: String,
120 pub to_version: String,
121 pub description: String,
122 pub migration_guide: Option<String>,
123 pub affected_apis: Vec<String>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct AbiCompatibilityEntry {
129 pub version: AbiVersion,
130 pub compatibility_with: HashMap<String, CompatibilityLevel>,
131 pub breaking_changes: Vec<BreakingChange>,
132 pub deprecation_notices: Vec<String>,
133}
134
135impl AbiCompatibilityEntry {
136 pub fn new(version: AbiVersion) -> Self {
137 Self {
138 version,
139 compatibility_with: HashMap::new(),
140 breaking_changes: Vec::new(),
141 deprecation_notices: Vec::new(),
142 }
143 }
144
145 pub fn is_compatible_with(&self, other_version: &str) -> bool {
146 matches!(
147 self.compatibility_with.get(other_version),
148 Some(CompatibilityLevel::Compatible) | Some(CompatibilityLevel::Recommended)
149 )
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct PlatformSupportEntry {
156 pub platform_arch: PlatformArch,
157 pub support_level: CompatibilityLevel,
158 pub min_version: Option<String>,
159 pub max_version: Option<String>,
160 pub notes: Option<String>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct DependencyCompatibility {
166 pub dependency_name: String,
167 pub required_version: String,
168 pub compatible_versions: Vec<String>,
169 pub incompatible_versions: Vec<String>,
170 pub notes: Option<String>,
171}
172
173pub struct PluginCompatibilityMatrix {
175 plugin_id: String,
176 current_version: String,
177 abi_compatibility: HashMap<AbiVersion, AbiCompatibilityEntry>,
178 platform_support: HashMap<PlatformArch, PlatformSupportEntry>,
179 dependency_compatibility: HashMap<String, DependencyCompatibility>,
180}
181
182impl PluginCompatibilityMatrix {
183 pub fn new(plugin_id: &str, current_version: &str) -> Self {
185 Self {
186 plugin_id: plugin_id.to_string(),
187 current_version: current_version.to_string(),
188 abi_compatibility: HashMap::new(),
189 platform_support: HashMap::new(),
190 dependency_compatibility: HashMap::new(),
191 }
192 }
193
194 pub fn add_abi_compatibility(&mut self, entry: AbiCompatibilityEntry) {
196 self.abi_compatibility.insert(entry.version.clone(), entry);
197 }
198
199 pub fn add_platform_support(&mut self, entry: PlatformSupportEntry) {
201 self.platform_support
202 .insert(entry.platform_arch.clone(), entry);
203 }
204
205 pub fn add_dependency_compatibility(&mut self, compat: DependencyCompatibility) {
207 self.dependency_compatibility
208 .insert(compat.dependency_name.clone(), compat);
209 }
210
211 pub fn check_abi_compatibility(&self, v1: &AbiVersion, v2: &AbiVersion) -> CompatibilityLevel {
213 if let Some(entry) = self.abi_compatibility.get(v1) {
214 entry
215 .compatibility_with
216 .get(&v2.to_string_val())
217 .copied()
218 .unwrap_or(CompatibilityLevel::Incompatible)
219 } else {
220 if v1.is_compatible_with(v2) {
222 CompatibilityLevel::Compatible
223 } else {
224 CompatibilityLevel::Incompatible
225 }
226 }
227 }
228
229 pub fn check_platform_support(&self, platform: &PlatformArch) -> CompatibilityLevel {
231 self.platform_support
232 .get(platform)
233 .map(|entry| entry.support_level)
234 .unwrap_or(CompatibilityLevel::Compatible)
235 }
236
237 pub fn supported_platforms(&self) -> Vec<PlatformArch> {
239 self.platform_support
240 .values()
241 .filter(|entry| entry.support_level != CompatibilityLevel::Incompatible)
242 .map(|entry| entry.platform_arch.clone())
243 .collect()
244 }
245
246 pub fn check_dependency_version(&self, dep_name: &str, version: &str) -> CompatibilityLevel {
248 if let Some(compat) = self.dependency_compatibility.get(dep_name) {
249 if compat.compatible_versions.contains(&version.to_string()) {
250 CompatibilityLevel::Compatible
251 } else if compat.incompatible_versions.contains(&version.to_string()) {
252 CompatibilityLevel::Incompatible
253 } else {
254 CompatibilityLevel::Compatible
255 }
256 } else {
257 CompatibilityLevel::Compatible
258 }
259 }
260
261 pub fn find_breaking_changes(
263 &self,
264 from_version: &str,
265 to_version: &str,
266 ) -> Vec<BreakingChange> {
267 let mut changes = Vec::new();
268 for entry in self.abi_compatibility.values() {
269 for change in &entry.breaking_changes {
270 if change.from_version == from_version && change.to_version == to_version {
271 changes.push(change.clone());
272 }
273 }
274 }
275 changes
276 }
277
278 pub fn analyze_abi_version(&self, version: &AbiVersion) -> CompatibilityAnalysis {
280 let mut compatible_versions = Vec::new();
281 let mut incompatible_versions = Vec::new();
282 let mut breaking_changes_summary = Vec::new();
283
284 if let Some(entry) = self.abi_compatibility.get(version) {
285 for (ver_str, level) in &entry.compatibility_with {
286 match level {
287 CompatibilityLevel::Compatible | CompatibilityLevel::Recommended => {
288 compatible_versions.push(ver_str.clone());
289 }
290 CompatibilityLevel::Incompatible => {
291 incompatible_versions.push(ver_str.clone());
292 }
293 _ => {}
294 }
295 }
296
297 for change in &entry.breaking_changes {
298 breaking_changes_summary.push(change.description.clone());
299 }
300 }
301
302 CompatibilityAnalysis {
303 version: version.to_string_val(),
304 compatible_versions,
305 incompatible_versions,
306 breaking_changes: breaking_changes_summary,
307 supported_platforms: self
308 .supported_platforms()
309 .iter()
310 .map(|p| p.as_string())
311 .collect(),
312 deprecation_status: self.abi_compatibility.get(version).and_then(|e| {
313 if e.deprecation_notices.is_empty() {
314 None
315 } else {
316 Some(e.deprecation_notices.clone())
317 }
318 }),
319 }
320 }
321
322 pub fn all_abi_versions(&self) -> Vec<AbiVersion> {
324 self.abi_compatibility.keys().cloned().collect()
325 }
326
327 pub fn generate_report(&self) -> CompatibilityReport {
329 CompatibilityReport {
330 plugin_id: self.plugin_id.clone(),
331 current_version: self.current_version.clone(),
332 total_abi_versions: self.abi_compatibility.len(),
333 supported_platform_count: self
334 .platform_support
335 .values()
336 .filter(|p| p.support_level != CompatibilityLevel::Incompatible)
337 .count(),
338 tracked_dependencies: self.dependency_compatibility.len(),
339 }
340 }
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct CompatibilityAnalysis {
346 pub version: String,
347 pub compatible_versions: Vec<String>,
348 pub incompatible_versions: Vec<String>,
349 pub breaking_changes: Vec<String>,
350 pub supported_platforms: Vec<String>,
351 pub deprecation_status: Option<Vec<String>>,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct CompatibilityReport {
357 pub plugin_id: String,
358 pub current_version: String,
359 pub total_abi_versions: usize,
360 pub supported_platform_count: usize,
361 pub tracked_dependencies: usize,
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn test_abi_version_creation() {
370 let v1 = AbiVersion::new(1, 0);
371 assert_eq!(v1.major, 1);
372 assert_eq!(v1.minor, 0);
373 }
374
375 #[test]
376 fn test_abi_version_try_parse() {
377 let v = AbiVersion::try_parse("2.1").unwrap();
378 assert_eq!(v.major, 2);
379 assert_eq!(v.minor, 1);
380 }
381
382 #[test]
383 fn test_abi_version_to_string() {
384 let v = AbiVersion::new(1, 5);
385 assert_eq!(v.to_string_val(), "1.5");
386 }
387
388 #[test]
389 fn test_abi_version_is_compatible() {
390 let v1 = AbiVersion::new(1, 0);
391 let v2 = AbiVersion::new(1, 5);
392 assert!(v1.is_compatible_with(&v2));
393 }
394
395 #[test]
396 fn test_abi_version_incompatible_major() {
397 let v1 = AbiVersion::new(1, 0);
398 let v2 = AbiVersion::new(2, 0);
399 assert!(!v1.is_compatible_with(&v2));
400 }
401
402 #[test]
403 fn test_abi_version_backward_compatible() {
404 let v_new = AbiVersion::new(1, 5);
405 let v_old = AbiVersion::new(1, 0);
406 assert!(v_new.is_backward_compatible(&v_old));
407 }
408
409 #[test]
410 fn test_abi_version_backward_incompatible() {
411 let v_old = AbiVersion::new(1, 5);
412 let v_new = AbiVersion::new(1, 0);
413 assert!(!v_new.is_backward_compatible(&v_old));
414 }
415
416 #[test]
417 fn test_platform_arch_creation() {
418 let pa = PlatformArch::new("linux", "x86_64");
419 assert_eq!(pa.platform, "linux");
420 assert_eq!(pa.architecture, "x86_64");
421 }
422
423 #[test]
424 fn test_platform_arch_as_string() {
425 let pa = PlatformArch::new("linux", "x86_64");
426 assert_eq!(pa.as_string(), "linux-x86_64");
427 }
428
429 #[test]
430 fn test_platform_arch_from_string() {
431 let pa = PlatformArch::from_string("linux-x86_64").unwrap();
432 assert_eq!(pa.platform, "linux");
433 assert_eq!(pa.architecture, "x86_64");
434 }
435
436 #[test]
437 fn test_compatibility_level_to_str() {
438 assert_eq!(CompatibilityLevel::Incompatible.as_str(), "Incompatible");
439 assert_eq!(CompatibilityLevel::Deprecated.as_str(), "Deprecated");
440 assert_eq!(CompatibilityLevel::Compatible.as_str(), "Compatible");
441 assert_eq!(CompatibilityLevel::Recommended.as_str(), "Recommended");
442 }
443
444 #[test]
445 fn test_compatibility_level_from_str() {
446 assert_eq!(
447 CompatibilityLevel::try_parse("Compatible"),
448 Some(CompatibilityLevel::Compatible)
449 );
450 assert_eq!(
451 CompatibilityLevel::try_parse("Incompatible"),
452 Some(CompatibilityLevel::Incompatible)
453 );
454 }
455
456 #[test]
457 fn test_compatibility_level_ordering() {
458 assert!(CompatibilityLevel::Incompatible < CompatibilityLevel::Deprecated);
459 assert!(CompatibilityLevel::Deprecated < CompatibilityLevel::Compatible);
460 assert!(CompatibilityLevel::Compatible < CompatibilityLevel::Recommended);
461 }
462
463 #[test]
464 fn test_abi_compatibility_entry_creation() {
465 let v = AbiVersion::new(1, 0);
466 let entry = AbiCompatibilityEntry::new(v);
467 assert_eq!(entry.version.major, 1);
468 assert_eq!(entry.breaking_changes.len(), 0);
469 }
470
471 #[test]
472 fn test_breaking_change_creation() {
473 let change = BreakingChange {
474 from_version: "1.0".to_string(),
475 to_version: "2.0".to_string(),
476 description: "API changed".to_string(),
477 migration_guide: Some("See upgrade guide".to_string()),
478 affected_apis: vec!["plugin_init".to_string()],
479 };
480 assert_eq!(change.from_version, "1.0");
481 assert_eq!(change.affected_apis.len(), 1);
482 }
483
484 #[test]
485 fn test_matrix_creation() {
486 let matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
487 assert_eq!(matrix.plugin_id, "test-plugin");
488 assert_eq!(matrix.current_version, "1.0.0");
489 }
490
491 #[test]
492 fn test_matrix_add_abi_compatibility() {
493 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
494 let entry = AbiCompatibilityEntry::new(AbiVersion::new(1, 0));
495 matrix.add_abi_compatibility(entry);
496 assert_eq!(matrix.abi_compatibility.len(), 1);
497 }
498
499 #[test]
500 fn test_matrix_add_platform_support() {
501 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
502 let pa = PlatformArch::new("linux", "x86_64");
503 let entry = PlatformSupportEntry {
504 platform_arch: pa,
505 support_level: CompatibilityLevel::Recommended,
506 min_version: None,
507 max_version: None,
508 notes: None,
509 };
510 matrix.add_platform_support(entry);
511 assert_eq!(matrix.platform_support.len(), 1);
512 }
513
514 #[test]
515 fn test_matrix_check_abi_compatibility_default() {
516 let matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
517 let v1 = AbiVersion::new(1, 0);
518 let v2 = AbiVersion::new(1, 5);
519 let level = matrix.check_abi_compatibility(&v1, &v2);
520 assert_eq!(level, CompatibilityLevel::Compatible);
521 }
522
523 #[test]
524 fn test_matrix_supported_platforms() {
525 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
526
527 let pa1 = PlatformArch::new("linux", "x86_64");
528 let entry1 = PlatformSupportEntry {
529 platform_arch: pa1,
530 support_level: CompatibilityLevel::Recommended,
531 min_version: None,
532 max_version: None,
533 notes: None,
534 };
535 matrix.add_platform_support(entry1);
536
537 let pa2 = PlatformArch::new("macos", "arm64");
538 let entry2 = PlatformSupportEntry {
539 platform_arch: pa2,
540 support_level: CompatibilityLevel::Compatible,
541 min_version: None,
542 max_version: None,
543 notes: None,
544 };
545 matrix.add_platform_support(entry2);
546
547 let platforms = matrix.supported_platforms();
548 assert_eq!(platforms.len(), 2);
549 }
550
551 #[test]
552 fn test_matrix_check_platform_support() {
553 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
554 let pa = PlatformArch::new("linux", "x86_64");
555 let entry = PlatformSupportEntry {
556 platform_arch: pa.clone(),
557 support_level: CompatibilityLevel::Recommended,
558 min_version: None,
559 max_version: None,
560 notes: None,
561 };
562 matrix.add_platform_support(entry);
563
564 let level = matrix.check_platform_support(&pa);
565 assert_eq!(level, CompatibilityLevel::Recommended);
566 }
567
568 #[test]
569 fn test_matrix_check_dependency_version() {
570 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
571 let compat = DependencyCompatibility {
572 dependency_name: "libfoo".to_string(),
573 required_version: "1.0".to_string(),
574 compatible_versions: vec!["1.0".to_string(), "1.1".to_string()],
575 incompatible_versions: vec!["2.0".to_string()],
576 notes: None,
577 };
578 matrix.add_dependency_compatibility(compat);
579
580 let level = matrix.check_dependency_version("libfoo", "1.0");
581 assert_eq!(level, CompatibilityLevel::Compatible);
582
583 let level_incompat = matrix.check_dependency_version("libfoo", "2.0");
584 assert_eq!(level_incompat, CompatibilityLevel::Incompatible);
585 }
586
587 #[test]
588 fn test_matrix_generate_report() {
589 let matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
590 let report = matrix.generate_report();
591 assert_eq!(report.plugin_id, "test-plugin");
592 assert_eq!(report.current_version, "1.0.0");
593 }
594
595 #[test]
596 fn test_compatibility_analysis_serialization() {
597 let analysis = CompatibilityAnalysis {
598 version: "1.0".to_string(),
599 compatible_versions: vec!["1.1".to_string()],
600 incompatible_versions: vec!["2.0".to_string()],
601 breaking_changes: vec![],
602 supported_platforms: vec!["linux-x86_64".to_string()],
603 deprecation_status: None,
604 };
605
606 let json = serde_json::to_string(&analysis).unwrap();
607 let deserialized: CompatibilityAnalysis = serde_json::from_str(&json).unwrap();
608
609 assert_eq!(deserialized.version, analysis.version);
610 assert_eq!(deserialized.compatible_versions.len(), 1);
611 }
612
613 #[test]
614 fn test_platform_support_entry_serialization() {
615 let pa = PlatformArch::new("linux", "x86_64");
616 let entry = PlatformSupportEntry {
617 platform_arch: pa,
618 support_level: CompatibilityLevel::Recommended,
619 min_version: Some("1.0".to_string()),
620 max_version: Some("2.0".to_string()),
621 notes: Some("Fully supported".to_string()),
622 };
623
624 let json = serde_json::to_string(&entry).unwrap();
625 let deserialized: PlatformSupportEntry = serde_json::from_str(&json).unwrap();
626
627 assert_eq!(deserialized.support_level, CompatibilityLevel::Recommended);
628 }
629
630 #[test]
631 fn test_dependency_compatibility_serialization() {
632 let compat = DependencyCompatibility {
633 dependency_name: "libfoo".to_string(),
634 required_version: "1.0".to_string(),
635 compatible_versions: vec!["1.0".to_string(), "1.1".to_string()],
636 incompatible_versions: vec!["2.0".to_string()],
637 notes: Some("Requires libfoo >= 1.0".to_string()),
638 };
639
640 let json = serde_json::to_string(&compat).unwrap();
641 let deserialized: DependencyCompatibility = serde_json::from_str(&json).unwrap();
642
643 assert_eq!(deserialized.dependency_name, "libfoo");
644 assert_eq!(deserialized.compatible_versions.len(), 2);
645 }
646
647 #[test]
648 fn test_matrix_find_breaking_changes() {
649 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
650
651 let change = BreakingChange {
652 from_version: "1.0".to_string(),
653 to_version: "2.0".to_string(),
654 description: "API changed".to_string(),
655 migration_guide: None,
656 affected_apis: vec!["plugin_init".to_string()],
657 };
658
659 let mut entry = AbiCompatibilityEntry::new(AbiVersion::new(2, 0));
660 entry.breaking_changes.push(change);
661 matrix.add_abi_compatibility(entry);
662
663 let changes = matrix.find_breaking_changes("1.0", "2.0");
664 assert_eq!(changes.len(), 1);
665 assert_eq!(changes[0].from_version, "1.0");
666 }
667
668 #[test]
669 fn test_matrix_analyze_abi_version() {
670 let mut matrix = PluginCompatibilityMatrix::new("test-plugin", "1.0.0");
671
672 let mut entry = AbiCompatibilityEntry::new(AbiVersion::new(1, 0));
673 entry
674 .compatibility_with
675 .insert("1.1".to_string(), CompatibilityLevel::Compatible);
676 entry
677 .compatibility_with
678 .insert("2.0".to_string(), CompatibilityLevel::Incompatible);
679
680 matrix.add_abi_compatibility(entry);
681
682 let analysis = matrix.analyze_abi_version(&AbiVersion::new(1, 0));
683 assert_eq!(analysis.compatible_versions.len(), 1);
684 assert_eq!(analysis.incompatible_versions.len(), 1);
685 }
686}