1use std::fmt;
8use std::sync::{Mutex, OnceLock};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Version {
13 pub major: u32,
15 pub minor: u32,
17 pub patch: u32,
19}
20
21impl Version {
22 pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
24 Self {
25 major,
26 minor,
27 patch,
28 }
29 }
30
31 pub fn parse(versionstr: &str) -> Result<Self, String> {
33 let base_version = versionstr.split('-').next().unwrap_or(versionstr);
35
36 let parts: Vec<&str> = base_version.split('.').collect();
38
39 if parts.len() < 3 {
40 return Err(format!("Invalid version format: {versionstr}"));
41 }
42
43 let major = parts[0].parse::<u32>().map_err(|_| parts[0].to_string())?;
44 let minor = parts[1].parse::<u32>().map_err(|_| parts[1].to_string())?;
45 let patch = parts[2].parse::<u32>().map_err(|_| parts[2].to_string())?;
46
47 Ok(Self::new(major, minor, patch))
48 }
49
50 pub const CURRENT: Self = Self::new(0, 1, 0);
52
53 pub const CURRENT_BETA: &'static str = "0.1.0-beta.4";
55
56 pub fn is_compatible_with(&self, other: &Version) -> bool {
58 self.major == other.major
60 && (self.minor > other.minor
61 || (self.minor == other.minor && self.patch >= other.patch))
62 }
63}
64
65impl fmt::Display for Version {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
68 }
69}
70
71pub trait Versioned {
73 fn since_version() -> Version;
75
76 fn deprecated_version() -> Option<Version> {
78 None
79 }
80
81 fn is_available_in(version: &Version) -> bool {
83 version.is_compatible_with(&Self::since_version())
84 && Self::deprecated_version()
85 .map(|dep| version < &dep)
86 .unwrap_or(true)
87 }
88}
89
90#[macro_export]
92macro_rules! since_version {
93 ($major:expr, $minor:expr, $patch:expr) => {
94 fn since_version() -> $crate::apiversioning::Version {
95 $crate::apiversioning::Version::new($major, $minor, $patch)
96 }
97 };
98}
99
100#[macro_export]
102macro_rules! deprecated_in {
103 ($major:expr, $minor:expr, $patch:expr) => {
104 fn deprecated_version() -> Option<$crate::apiversioning::Version> {
105 Some($crate::apiversioning::Version::new($major, $minor, $patch))
106 }
107 };
108}
109
110pub struct VersionRegistry {
112 entries: Vec<ApiEntry>,
113}
114
115#[derive(Debug, Clone)]
116pub struct ApiEntry {
117 pub name: String,
118 pub module: String,
119 pub since: Version,
120 pub deprecated: Option<Version>,
121 pub replacement: Option<String>,
122 pub breakingchanges: Vec<BreakingChange>,
123 pub example_usage: Option<String>,
124 pub migration_example: Option<String>,
125}
126
127#[derive(Debug, Clone)]
128pub struct BreakingChange {
129 pub version: Version,
130 pub description: String,
131 pub impact: BreakingChangeImpact,
132 pub mitigation: String,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum BreakingChangeImpact {
137 Low, Medium, High, Critical, }
142
143impl VersionRegistry {
144 pub fn new() -> Self {
146 Self {
147 entries: Vec::new(),
148 }
149 }
150
151 pub fn register_api(
153 &mut self,
154 name: impl Into<String>,
155 module: impl Into<String>,
156 since: Version,
157 ) -> &mut Self {
158 let name_str = name.into();
159 let module_str = module.into();
160
161 if !self
163 .entries
164 .iter()
165 .any(|e| e.name == name_str && e.module == module_str)
166 {
167 self.entries.push(ApiEntry {
168 name: name_str,
169 module: module_str,
170 since,
171 deprecated: None,
172 replacement: None,
173 breakingchanges: Vec::new(),
174 example_usage: None,
175 migration_example: None,
176 });
177 }
178 self
179 }
180
181 pub fn register_api_with_example(
183 &mut self,
184 name: impl Into<String>,
185 module: impl Into<String>,
186 since: Version,
187 example: impl Into<String>,
188 ) -> &mut Self {
189 let name_str = name.into();
190 let module_str = module.into();
191
192 if !self
193 .entries
194 .iter()
195 .any(|e| e.name == name_str && e.module == module_str)
196 {
197 self.entries.push(ApiEntry {
198 name: name_str,
199 module: module_str,
200 since,
201 deprecated: None,
202 replacement: None,
203 breakingchanges: Vec::new(),
204 example_usage: Some(example.into()),
205 migration_example: None,
206 });
207 }
208 self
209 }
210
211 pub fn deprecate_api(
213 &mut self,
214 name: &str,
215 version: Version,
216 replacement: Option<String>,
217 ) -> Result<(), String> {
218 if let Some(entry) = self.entries.iter_mut().find(|e| e.name == name) {
219 entry.deprecated = Some(version);
220 entry.replacement = replacement;
221 Ok(())
222 } else {
223 Err(format!("API '{name}' not found in registry"))
224 }
225 }
226
227 pub fn deprecate_api_with_example(
229 &mut self,
230 name: &str,
231 version: Version,
232 replacement: Option<String>,
233 migration_example: impl Into<String>,
234 ) -> Result<(), String> {
235 if let Some(entry) = self.entries.iter_mut().find(|e| e.name == name) {
236 entry.deprecated = Some(version);
237 entry.replacement = replacement;
238 entry.migration_example = Some(migration_example.into());
239 Ok(())
240 } else {
241 Err(format!("API '{name}' not found in registry"))
242 }
243 }
244
245 pub fn add_breaking_change(
247 &mut self,
248 apiname: &str,
249 change: BreakingChange,
250 ) -> Result<(), String> {
251 if let Some(entry) = self.entries.iter_mut().find(|e| e.name == apiname) {
252 entry.breakingchanges.push(change);
253 Ok(())
254 } else {
255 Err(format!("API '{apiname}' not found in registry"))
256 }
257 }
258
259 pub fn apis_in_version(&self, version: &Version) -> Vec<&ApiEntry> {
261 self.entries
262 .iter()
263 .filter(|entry| {
264 version.is_compatible_with(&entry.since)
265 && entry.deprecated.map(|dep| version < &dep).unwrap_or(true)
266 })
267 .collect()
268 }
269
270 pub fn deprecated_apis_in_version(&self, version: &Version) -> Vec<&ApiEntry> {
272 self.entries
273 .iter()
274 .filter(|entry| entry.deprecated.map(|dep| version >= &dep).unwrap_or(false))
275 .collect()
276 }
277
278 pub fn migration_guide(&self, from: &Version, to: &Version) -> String {
280 let mut guide = format!("# Migration Guide: {from} â {to}\n\n");
281
282 guide.push_str(&format!(
283 "This guide helps you upgrade from scirs2-core {from} to {to}.\n\n"
284 ));
285
286 let breakingchanges: Vec<_> = self
288 .entries
289 .iter()
290 .filter_map(|e| {
291 if !e.breakingchanges.is_empty() {
292 let relevant_changes: Vec<_> = e
293 .breakingchanges
294 .iter()
295 .filter(|bc| bc.version > *from && bc.version <= *to)
296 .collect();
297 if !relevant_changes.is_empty() {
298 Some((e, relevant_changes))
299 } else {
300 None
301 }
302 } else {
303 None
304 }
305 })
306 .collect();
307
308 if !breakingchanges.is_empty() {
309 guide.push_str("## â ī¸ Breaking Changes\n\n");
310
311 for (api, changes) in breakingchanges {
312 guide.push_str(&format!(
313 "### {name} ({module})\n\n",
314 name = api.name,
315 module = api.module
316 ));
317
318 for change in changes {
319 let impact_icon = match change.impact {
320 BreakingChangeImpact::Low => "đĄ",
321 BreakingChangeImpact::Medium => "đ ",
322 BreakingChangeImpact::High => "đ´",
323 BreakingChangeImpact::Critical => "đĨ",
324 };
325
326 guide.push_str(&format!(
327 "{} **{}**: {}\n\n**Mitigation**: {}\n\n",
328 impact_icon, change.version, change.description, change.mitigation
329 ));
330 }
331 }
332 }
333
334 let removed: Vec<_> = self
336 .entries
337 .iter()
338 .filter(|e| {
339 from.is_compatible_with(&e.since)
340 && e.deprecated.map(|d| to >= &d && from < &d).unwrap_or(false)
341 })
342 .collect();
343
344 if !removed.is_empty() {
345 guide.push_str("## đī¸ Removed APIs\n\n");
346
347 for api in removed {
348 guide.push_str(&format!(
349 "### {name} ({module})\n\n",
350 name = api.name,
351 module = api.module
352 ));
353
354 if let Some(ref replacement) = api.replacement {
355 guide.push_str(&format!("**Replacement**: {replacement}\n\n"));
356 }
357
358 if let Some(ref migration_example) = api.migration_example {
359 guide.push_str("**Migration Example**:\n\n");
360 guide.push_str("```rust\n");
361 guide.push_str(migration_example);
362 guide.push_str("\n```\n\n");
363 }
364 }
365 }
366
367 let new_apis: Vec<_> = self
369 .entries
370 .iter()
371 .filter(|e| to.is_compatible_with(&e.since) && !from.is_compatible_with(&e.since))
372 .collect();
373
374 if !new_apis.is_empty() {
375 guide.push_str("## ⨠New APIs\n\n");
376
377 for api in new_apis {
378 guide.push_str(&format!(
379 "### {} ({}) - Since {}\n\n",
380 api.name, api.module, api.since
381 ));
382
383 if let Some(ref example) = api.example_usage {
384 guide.push_str("**Usage Example**:\n\n");
385 guide.push_str("```rust\n");
386 guide.push_str(example);
387 guide.push_str("\n```\n\n");
388 }
389 }
390 }
391
392 guide.push_str("## đ Migration Checklist\n\n");
394 guide.push_str("- [ ] Update Cargo.toml dependencies\n");
395 guide.push_str("- [ ] Fix compilation errors\n");
396 guide.push_str("- [ ] Update deprecated API usage\n");
397 guide.push_str("- [ ] Run test suite\n");
398 guide.push_str("- [ ] Update documentation\n");
399 guide.push_str("- [ ] Performance testing\n\n");
400
401 guide.push_str("## đ Additional Resources\n\n");
402 guide.push_str(&format!(
403 "- [API Documentation](https://docs.rs/scirs2-core/{to})\n"
404 ));
405 guide.push_str(
406 "- [Changelog](https://github.com/cool-japan/scirs/blob/main/CHANGELOG.md)\n",
407 );
408 guide.push_str("- [Examples](https://github.com/cool-japan/scirs/tree/main/examples)\n");
409
410 guide
411 }
412
413 pub fn deprecation_timeline(&self) -> String {
415 let mut timeline = String::from("# API Deprecation Timeline\n\n");
416
417 let mut deprecated_apis: Vec<_> = self
418 .entries
419 .iter()
420 .filter(|e| e.deprecated.is_some())
421 .collect();
422
423 deprecated_apis.sort_by_key(|e| e.deprecated.unwrap());
424
425 let mut current_version: Option<Version> = None;
426
427 for api in deprecated_apis {
428 let dep_version = api.deprecated.unwrap();
429
430 if current_version != Some(dep_version) {
431 timeline.push_str(&format!("\n## Version {dep_version}\n\n"));
432 current_version = Some(dep_version);
433 }
434
435 timeline.push_str(&format!(
436 "- **{name}** ({module})",
437 name = api.name,
438 module = api.module
439 ));
440
441 if let Some(ref replacement) = api.replacement {
442 timeline.push_str(&format!(" â {replacement}"));
443 }
444
445 timeline.push('\n');
446 }
447
448 timeline
449 }
450}
451
452impl Default for VersionRegistry {
453 fn default() -> Self {
454 Self::new()
455 }
456}
457
458#[derive(Debug, Clone, PartialEq, Eq)]
460pub enum ApiFreezeStatus {
461 Unfrozen,
463 Frozen,
465 Stable,
467}
468
469#[derive(Debug, Clone)]
471pub struct ApiCompatibilityChecker {
472 freeze_status: ApiFreezeStatus,
474 frozen_apis: Vec<ApiSignature>,
476 #[allow(dead_code)]
478 compatibility_rules: Vec<CompatibilityRule>,
479}
480
481#[derive(Debug, Clone, PartialEq, Eq, Hash)]
483pub struct ApiSignature {
484 pub name: String,
485 pub module: String,
486 pub signature_hash: u64,
487 pub parameters: Vec<Parameter>,
488 pub return_type: Option<String>,
489 pub visibility: Visibility,
490 pub stability: StabilityLevel,
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Hash)]
495pub struct Parameter {
496 pub name: String,
497 pub type_name: String,
498 pub is_optional: bool,
499 pub defaultvalue: Option<String>,
500}
501
502#[derive(Debug, Clone, PartialEq, Eq, Hash)]
504pub enum Visibility {
505 Public,
506 PublicCrate,
507 Private,
508}
509
510#[derive(Debug, Clone, PartialEq, Eq, Hash)]
512pub enum StabilityLevel {
513 Stable,
514 Unstable,
515 Deprecated,
516 Experimental,
517}
518
519#[derive(Debug, Clone)]
521pub struct CompatibilityRule {
522 pub rule_type: CompatibilityRuleType,
523 pub description: String,
524 pub severity: CompatibilitySeverity,
525 pub auto_fix: Option<String>,
526}
527
528#[derive(Debug, Clone, PartialEq, Eq)]
530pub enum CompatibilityRuleType {
531 SignatureChange,
533 PublicApiRemoval,
535 ParameterTypeChange,
537 ReturnTypeChange,
539 NewRequiredParameter,
541 VisibilityReduction,
543}
544
545#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
547pub enum CompatibilitySeverity {
548 Breaking,
550 Warning,
552 Info,
554}
555
556#[derive(Debug, Clone)]
558pub struct CompatibilityCheckResult {
559 pub is_compatible: bool,
560 pub violations: Vec<CompatibilityViolation>,
561 pub warnings: Vec<CompatibilityWarning>,
562 pub suggestions: Vec<CompatibilitySuggestion>,
563}
564
565#[derive(Debug, Clone)]
567pub struct CompatibilityViolation {
568 pub apiname: String,
569 pub violation_type: CompatibilityRuleType,
570 pub severity: CompatibilitySeverity,
571 pub description: String,
572 pub old_signature: Option<ApiSignature>,
573 pub new_signature: Option<ApiSignature>,
574 pub fix_suggestion: Option<String>,
575}
576
577#[derive(Debug, Clone)]
579pub struct CompatibilityWarning {
580 pub apiname: String,
581 pub message: String,
582 pub impact: String,
583}
584
585#[derive(Debug, Clone)]
587pub struct CompatibilitySuggestion {
588 pub apiname: String,
589 pub suggestion: String,
590 pub rationale: String,
591}
592
593impl ApiCompatibilityChecker {
594 pub fn new() -> Self {
596 Self {
597 freeze_status: ApiFreezeStatus::Unfrozen,
598 frozen_apis: Vec::new(),
599 compatibility_rules: Self::default_compatibility_rules(),
600 }
601 }
602
603 pub fn for_beta1() -> Self {
605 Self {
606 freeze_status: ApiFreezeStatus::Frozen,
607 frozen_apis: Vec::new(),
608 compatibility_rules: Self::beta1_compatibility_rules(),
609 }
610 }
611
612 fn default_compatibility_rules() -> Vec<CompatibilityRule> {
614 vec![
615 CompatibilityRule {
616 rule_type: CompatibilityRuleType::PublicApiRemoval,
617 description: "Public APIs cannot be removed without deprecation".to_string(),
618 severity: CompatibilitySeverity::Breaking,
619 auto_fix: Some("Add deprecation annotation".to_string()),
620 },
621 CompatibilityRule {
622 rule_type: CompatibilityRuleType::SignatureChange,
623 description: "Function signatures cannot change in breaking ways".to_string(),
624 severity: CompatibilitySeverity::Breaking,
625 auto_fix: None,
626 },
627 CompatibilityRule {
628 rule_type: CompatibilityRuleType::ParameterTypeChange,
629 description: "Parameter types cannot change".to_string(),
630 severity: CompatibilitySeverity::Breaking,
631 auto_fix: Some("Create new function with different name".to_string()),
632 },
633 ]
634 }
635
636 fn beta1_compatibility_rules() -> Vec<CompatibilityRule> {
638 vec![
639 CompatibilityRule {
640 rule_type: CompatibilityRuleType::PublicApiRemoval,
641 description: "No public APIs can be removed during Beta 1 freeze".to_string(),
642 severity: CompatibilitySeverity::Breaking,
643 auto_fix: None,
644 },
645 CompatibilityRule {
646 rule_type: CompatibilityRuleType::SignatureChange,
647 description: "No function signatures can change during API freeze".to_string(),
648 severity: CompatibilitySeverity::Breaking,
649 auto_fix: None,
650 },
651 CompatibilityRule {
652 rule_type: CompatibilityRuleType::NewRequiredParameter,
653 description: "No new required parameters can be added during freeze".to_string(),
654 severity: CompatibilitySeverity::Breaking,
655 auto_fix: Some("Make parameter optional with default value".to_string()),
656 },
657 CompatibilityRule {
658 rule_type: CompatibilityRuleType::VisibilityReduction,
659 description: "API visibility cannot be reduced during freeze".to_string(),
660 severity: CompatibilitySeverity::Breaking,
661 auto_fix: None,
662 },
663 ]
664 }
665
666 pub fn freeze_apis(&mut self, apis: Vec<ApiSignature>) -> Result<(), String> {
668 if self.freeze_status == ApiFreezeStatus::Stable {
669 return Err("Cannot freeze APIs that are already stable".to_string());
670 }
671
672 self.frozen_apis = apis;
673 self.freeze_status = ApiFreezeStatus::Frozen;
674 Ok(())
675 }
676
677 pub fn check_compatibility(&self, currentapis: &[ApiSignature]) -> CompatibilityCheckResult {
679 let mut violations = Vec::new();
680 let mut warnings = Vec::new();
681 let mut suggestions = Vec::new();
682
683 if self.freeze_status == ApiFreezeStatus::Unfrozen {
684 warnings.push(CompatibilityWarning {
685 apiname: "*".to_string(),
686 message: "APIs are not frozen - no compatibility checking performed".to_string(),
687 impact: "API changes may break compatibility".to_string(),
688 });
689
690 return CompatibilityCheckResult {
691 is_compatible: true,
692 violations,
693 warnings,
694 suggestions,
695 };
696 }
697
698 for frozen_api in &self.frozen_apis {
700 if frozen_api.visibility == Visibility::Public
701 && frozen_api.stability == StabilityLevel::Stable
702 && !currentapis
703 .iter()
704 .any(|api| api.name == frozen_api.name && api.module == frozen_api.module)
705 {
706 violations.push(CompatibilityViolation {
707 apiname: format!(
708 "{module}::{name}",
709 module = frozen_api.module,
710 name = frozen_api.name
711 ),
712 violation_type: CompatibilityRuleType::PublicApiRemoval,
713 severity: CompatibilitySeverity::Breaking,
714 description: "Public stable API was removed".to_string(),
715 old_signature: Some(frozen_api.clone()),
716 new_signature: None,
717 fix_suggestion: Some("Restore the API or mark as deprecated".to_string()),
718 });
719 }
720 }
721
722 for current_api in currentapis {
724 if let Some(frozen_api) = self
725 .frozen_apis
726 .iter()
727 .find(|api| api.name == current_api.name && api.module == current_api.module)
728 {
729 if frozen_api.parameters != current_api.parameters {
731 let has_new_required = current_api.parameters.iter().any(|p| {
732 !p.is_optional && !frozen_api.parameters.iter().any(|fp| fp.name == p.name)
733 });
734
735 if has_new_required {
736 violations.push(CompatibilityViolation {
737 apiname: format!(
738 "{module}::{name}",
739 module = current_api.module,
740 name = current_api.name
741 ),
742 violation_type: CompatibilityRuleType::NewRequiredParameter,
743 severity: CompatibilitySeverity::Breaking,
744 description: "New required parameter added".to_string(),
745 old_signature: Some(frozen_api.clone()),
746 new_signature: Some(current_api.clone()),
747 fix_suggestion: Some("Make new parameters optional".to_string()),
748 });
749 continue; }
751 }
752
753 if Self::is_visibility_reduced(&frozen_api.visibility, ¤t_api.visibility) {
755 violations.push(CompatibilityViolation {
756 apiname: format!(
757 "{module}::{name}",
758 module = current_api.module,
759 name = current_api.name
760 ),
761 violation_type: CompatibilityRuleType::VisibilityReduction,
762 severity: CompatibilitySeverity::Breaking,
763 description: "API visibility was reduced".to_string(),
764 old_signature: Some(frozen_api.clone()),
765 new_signature: Some(current_api.clone()),
766 fix_suggestion: Some("Restore original visibility".to_string()),
767 });
768 }
769
770 if frozen_api.return_type != current_api.return_type {
772 violations.push(CompatibilityViolation {
773 apiname: format!(
774 "{module}::{name}",
775 module = current_api.module,
776 name = current_api.name
777 ),
778 violation_type: CompatibilityRuleType::ReturnTypeChange,
779 severity: CompatibilitySeverity::Breaking,
780 description: "Return type changed".to_string(),
781 old_signature: Some(frozen_api.clone()),
782 new_signature: Some(current_api.clone()),
783 fix_suggestion: Some(
784 "Restore original return type or create new API".to_string(),
785 ),
786 });
787 }
788
789 if frozen_api.parameters.len() == current_api.parameters.len() {
791 for (old_param, new_param) in frozen_api
792 .parameters
793 .iter()
794 .zip(current_api.parameters.iter())
795 {
796 if old_param.name == new_param.name
797 && old_param.type_name != new_param.type_name
798 {
799 violations.push(CompatibilityViolation {
800 apiname: {
801 let module = ¤t_api.module;
802 let name = ¤t_api.name;
803 format!("{module}::{name}")
804 },
805 violation_type: CompatibilityRuleType::ParameterTypeChange,
806 severity: CompatibilitySeverity::Breaking,
807 description: format!(
808 "Parameter '{param_name}' type changed from '{old_type}' to '{new_type}'",
809 param_name = old_param.name, old_type = old_param.type_name, new_type = new_param.type_name
810 ),
811 old_signature: Some(frozen_api.clone()),
812 new_signature: Some(current_api.clone()),
813 fix_suggestion: Some(
814 "Restore original parameter type or create new API".to_string(),
815 ),
816 });
817 }
818 }
819 }
820
821 if frozen_api.signature_hash != current_api.signature_hash
823 && !violations.iter().any(|v| {
824 v.apiname
825 == format!(
826 "{module}::{name}",
827 module = current_api.module,
828 name = current_api.name
829 )
830 })
831 {
832 violations.push(CompatibilityViolation {
833 apiname: format!(
834 "{module}::{name}",
835 module = current_api.module,
836 name = current_api.name
837 ),
838 violation_type: CompatibilityRuleType::SignatureChange,
839 severity: CompatibilitySeverity::Breaking,
840 description: "API signature changed in an unspecified way".to_string(),
841 old_signature: Some(frozen_api.clone()),
842 new_signature: Some(current_api.clone()),
843 fix_suggestion: Some(
844 "Revert signature change or create new API".to_string(),
845 ),
846 });
847 }
848 }
849 }
850
851 for current_api in currentapis {
853 if !self
854 .frozen_apis
855 .iter()
856 .any(|api| api.name == current_api.name && api.module == current_api.module)
857 {
858 suggestions.push(CompatibilitySuggestion {
859 apiname: format!(
860 "{module}::{name}",
861 module = current_api.module,
862 name = current_api.name
863 ),
864 suggestion: "New API detected - ensure proper documentation".to_string(),
865 rationale: "New APIs should be well-documented and tested".to_string(),
866 });
867 }
868 }
869
870 let is_compatible = violations
871 .iter()
872 .all(|v| v.severity != CompatibilitySeverity::Breaking);
873
874 CompatibilityCheckResult {
875 is_compatible,
876 violations,
877 warnings,
878 suggestions,
879 }
880 }
881
882 fn is_visibility_reduced(old: &Visibility, new: &Visibility) -> bool {
884 use Visibility::*;
885 matches!(
886 (old, new),
887 (Public, PublicCrate) | (Public, Private) | (PublicCrate, Private)
888 )
889 }
890
891 pub fn generate_compatibility_report(&self, result: &CompatibilityCheckResult) -> String {
893 let mut report = String::from("# API Compatibility Report\n\n");
894
895 report.push_str(&format!("**Freeze Status**: {:?}\n", self.freeze_status));
896 report.push_str(&format!(
897 "**Overall Compatible**: {}\n\n",
898 result.is_compatible
899 ));
900
901 if !result.violations.is_empty() {
902 report.push_str("## â Compatibility Violations\n\n");
903 for violation in &result.violations {
904 let severity_icon = match violation.severity {
905 CompatibilitySeverity::Breaking => "đĨ",
906 CompatibilitySeverity::Warning => "â ī¸",
907 CompatibilitySeverity::Info => "âšī¸",
908 };
909
910 report.push_str(&format!(
911 "{} **{}** ({:?}): {}\n",
912 severity_icon,
913 violation.apiname,
914 violation.violation_type,
915 violation.description
916 ));
917
918 if let Some(ref fix) = violation.fix_suggestion {
919 report.push_str(&format!(" - **Fix**: {fix}\n"));
920 }
921 report.push('\n');
922 }
923 }
924
925 if !result.warnings.is_empty() {
926 report.push_str("## â ī¸ Warnings\n\n");
927 for warning in &result.warnings {
928 report.push_str(&format!(
929 "- **{}**: {} (Impact: {})\n",
930 warning.apiname, warning.message, warning.impact
931 ));
932 }
933 report.push('\n');
934 }
935
936 if !result.suggestions.is_empty() {
937 report.push_str("## đĄ Suggestions\n\n");
938 for suggestion in &result.suggestions {
939 report.push_str(&format!(
940 "- **{}**: {} ({})\n",
941 suggestion.apiname, suggestion.suggestion, suggestion.rationale
942 ));
943 }
944 report.push('\n');
945 }
946
947 report.push_str("## đ Next Steps\n\n");
948 if result.is_compatible {
949 report.push_str("â
All compatibility checks passed. Safe to proceed.\n");
950 } else {
951 report
952 .push_str("â Compatibility violations detected. Address issues before release:\n");
953 report.push_str("1. Review breaking changes listed above\n");
954 report.push_str("2. Apply suggested fixes or revert changes\n");
955 report.push_str("3. Re-run compatibility check\n");
956 report.push_str("4. Update documentation if needed\n");
957 }
958
959 report
960 }
961}
962
963impl Default for ApiCompatibilityChecker {
964 fn default() -> Self {
965 Self::new()
966 }
967}
968
969static REGISTRY: OnceLock<Mutex<VersionRegistry>> = OnceLock::new();
971
972static API_CHECKER: OnceLock<Mutex<ApiCompatibilityChecker>> = OnceLock::new();
974
975#[allow(dead_code)]
977pub fn global_registry_mut() -> std::sync::MutexGuard<'static, VersionRegistry> {
978 REGISTRY
979 .get_or_init(|| Mutex::new(VersionRegistry::new()))
980 .lock()
981 .unwrap()
982}
983
984#[allow(dead_code)]
986pub fn global_registry() -> &'static Mutex<VersionRegistry> {
987 REGISTRY.get_or_init(|| Mutex::new(VersionRegistry::new()))
988}
989
990#[allow(dead_code)]
992pub fn global_api_checker_mut() -> std::sync::MutexGuard<'static, ApiCompatibilityChecker> {
993 API_CHECKER
994 .get_or_init(|| Mutex::new(ApiCompatibilityChecker::for_beta1()))
995 .lock()
996 .unwrap()
997}
998
999#[allow(dead_code)]
1001pub fn global_api_checker() -> &'static Mutex<ApiCompatibilityChecker> {
1002 API_CHECKER.get_or_init(|| Mutex::new(ApiCompatibilityChecker::for_beta1()))
1003}
1004
1005#[allow(dead_code)]
1007pub fn create_api_signature(
1008 name: &str,
1009 module: &str,
1010 parameters: Vec<Parameter>,
1011 return_type: Option<String>,
1012 visibility: Visibility,
1013 stability: StabilityLevel,
1014) -> ApiSignature {
1015 let name_str = name.to_string();
1016 let module_str = module.to_string();
1017
1018 let mut hasher = std::collections::hash_map::DefaultHasher::new();
1020 use std::hash::{Hash, Hasher};
1021
1022 name_str.hash(&mut hasher);
1023 module_str.hash(&mut hasher);
1024 for param in ¶meters {
1026 param.name.hash(&mut hasher);
1027 param.type_name.hash(&mut hasher);
1028 param.is_optional.hash(&mut hasher);
1029 if let Some(ref default) = param.defaultvalue {
1030 default.hash(&mut hasher);
1031 }
1032 }
1033 return_type.hash(&mut hasher);
1034 visibility.hash(&mut hasher);
1035
1036 ApiSignature {
1037 name: name_str,
1038 module: module_str,
1039 signature_hash: hasher.finish(),
1040 parameters,
1041 return_type,
1042 visibility,
1043 stability,
1044 }
1045}
1046
1047#[allow(dead_code)]
1049pub fn initialize_beta1_freeze() -> Result<(), String> {
1050 let mut checker = global_api_checker_mut();
1051
1052 let core_apis = get_test_frozen_apis();
1054
1055 checker.freeze_apis(core_apis)
1056}
1057
1058#[allow(dead_code)]
1060fn get_test_frozen_apis() -> Vec<ApiSignature> {
1061 vec![
1062 create_api_signature(
1063 "simd_add",
1064 "simd_ops",
1065 vec![
1066 Parameter {
1067 name: "a".to_string(),
1068 type_name: "&ArrayView1<Self>".to_string(),
1069 is_optional: false,
1070 defaultvalue: None,
1071 },
1072 Parameter {
1073 name: "b".to_string(),
1074 type_name: "&ArrayView1<Self>".to_string(),
1075 is_optional: false,
1076 defaultvalue: None,
1077 },
1078 ],
1079 Some("Array1<Self>".to_string()),
1080 Visibility::Public,
1081 StabilityLevel::Stable,
1082 ),
1083 create_api_signature(
1084 "simd_mul",
1085 "simd_ops",
1086 vec![
1087 Parameter {
1088 name: "a".to_string(),
1089 type_name: "&ArrayView1<Self>".to_string(),
1090 is_optional: false,
1091 defaultvalue: None,
1092 },
1093 Parameter {
1094 name: "b".to_string(),
1095 type_name: "&ArrayView1<Self>".to_string(),
1096 is_optional: false,
1097 defaultvalue: None,
1098 },
1099 ],
1100 Some("Array1<Self>".to_string()),
1101 Visibility::Public,
1102 StabilityLevel::Stable,
1103 ),
1104 create_api_signature(
1105 "simd_dot",
1106 "simd_ops",
1107 vec![
1108 Parameter {
1109 name: "a".to_string(),
1110 type_name: "&ArrayView1<Self>".to_string(),
1111 is_optional: false,
1112 defaultvalue: None,
1113 },
1114 Parameter {
1115 name: "b".to_string(),
1116 type_name: "&ArrayView1<Self>".to_string(),
1117 is_optional: false,
1118 defaultvalue: None,
1119 },
1120 ],
1121 Some("Self".to_string()),
1122 Visibility::Public,
1123 StabilityLevel::Stable,
1124 ),
1125 create_api_signature(
1127 "simd_add_cache_optimized",
1128 "simd_ops",
1129 vec![
1130 Parameter {
1131 name: "a".to_string(),
1132 type_name: "&ArrayView1<Self>".to_string(),
1133 is_optional: false,
1134 defaultvalue: None,
1135 },
1136 Parameter {
1137 name: "b".to_string(),
1138 type_name: "&ArrayView1<Self>".to_string(),
1139 is_optional: false,
1140 defaultvalue: None,
1141 },
1142 ],
1143 Some("Array1<Self>".to_string()),
1144 Visibility::Public,
1145 StabilityLevel::Experimental,
1146 ),
1147 create_api_signature(
1148 "simd_fma_advanced_optimized",
1149 "simd_ops",
1150 vec![
1151 Parameter {
1152 name: "a".to_string(),
1153 type_name: "&ArrayView1<Self>".to_string(),
1154 is_optional: false,
1155 defaultvalue: None,
1156 },
1157 Parameter {
1158 name: "b".to_string(),
1159 type_name: "&ArrayView1<Self>".to_string(),
1160 is_optional: false,
1161 defaultvalue: None,
1162 },
1163 Parameter {
1164 name: "c".to_string(),
1165 type_name: "&ArrayView1<Self>".to_string(),
1166 is_optional: false,
1167 defaultvalue: None,
1168 },
1169 ],
1170 Some("Array1<Self>".to_string()),
1171 Visibility::Public,
1172 StabilityLevel::Experimental,
1173 ),
1174 create_api_signature(
1175 "Version",
1176 "apiversioning",
1177 vec![],
1178 None,
1179 Visibility::Public,
1180 StabilityLevel::Stable,
1181 ),
1182 create_api_signature(
1183 "ApiCompatibilityChecker",
1184 "apiversioning",
1185 vec![],
1186 None,
1187 Visibility::Public,
1188 StabilityLevel::Stable,
1189 ),
1190 ]
1191}
1192
1193#[allow(dead_code)]
1195pub fn check_current_compatibility() -> Result<CompatibilityCheckResult, String> {
1196 let checker = global_api_checker();
1197 let checker_guard = checker.lock().map_err(|e| e.to_string())?;
1198
1199 let current_apis = get_test_frozen_apis();
1202
1203 Ok((*checker_guard).check_compatibility(¤t_apis))
1204}
1205
1206#[cfg(test)]
1207mod tests {
1208 use super::*;
1209
1210 #[test]
1211 fn test_version_parse() {
1212 assert_eq!(Version::parse("1.2.3").unwrap(), Version::new(1, 2, 3));
1213 assert_eq!(
1214 Version::parse("0.1.0-beta.4").unwrap(),
1215 Version::new(0, 1, 0)
1216 );
1217 assert_eq!(
1218 Version::parse("10.20.30").unwrap(),
1219 Version::new(10, 20, 30)
1220 );
1221
1222 assert!(Version::parse("1.2").is_err());
1223 assert!(Version::parse("a.b.c").is_err());
1224 assert!(Version::parse("").is_err());
1225 }
1226
1227 #[test]
1228 fn test_version_compatibility() {
1229 let v1_0_0 = Version::new(1, 0, 0);
1230 let v1_1_0 = Version::new(1, 1, 0);
1231 let v1_0_1 = Version::new(1, 0, 1);
1232 let v2_0_0 = Version::new(2, 0, 0);
1233
1234 assert!(v1_1_0.is_compatible_with(&v1_0_0));
1235 assert!(v1_0_1.is_compatible_with(&v1_0_0));
1236 assert!(!v2_0_0.is_compatible_with(&v1_0_0));
1237 assert!(!v1_0_0.is_compatible_with(&v1_1_0));
1238 }
1239
1240 #[test]
1241 fn test_version_registry() {
1242 let mut registry = VersionRegistry::new();
1243
1244 registry
1245 .register_api("Array", "core", Version::new(0, 1, 0))
1246 .register_api("Matrix", "linalg", Version::new(0, 1, 0))
1247 .register_api("OldArray", "core", Version::new(0, 1, 0));
1248
1249 registry
1250 .deprecate_api("OldArray", Version::new(0, 2, 0), Some("Array".to_string()))
1251 .unwrap();
1252
1253 let v0_1_0 = Version::new(0, 1, 0);
1254 let v0_2_0 = Version::new(0, 2, 0);
1255
1256 let apis_v1 = registry.apis_in_version(&v0_1_0);
1258 assert_eq!(apis_v1.len(), 3);
1259
1260 let apis_v2 = registry.apis_in_version(&v0_2_0);
1262 assert_eq!(apis_v2.len(), 2); let deprecated = registry.deprecated_apis_in_version(&v0_2_0);
1266 assert_eq!(deprecated.len(), 1);
1267 assert_eq!(deprecated[0].name, "OldArray");
1268 }
1269
1270 #[test]
1271 fn test_migration_guide() {
1272 let mut registry = VersionRegistry::new();
1273
1274 registry
1275 .register_api("Feature1", "module1", Version::new(0, 1, 0))
1276 .register_api("Feature2", "module2", Version::new(0, 2, 0))
1277 .register_api("OldFeature", "module1", Version::new(0, 1, 0));
1278
1279 registry
1280 .deprecate_api(
1281 "OldFeature",
1282 Version::new(0, 2, 0),
1283 Some("Feature2".to_string()),
1284 )
1285 .unwrap();
1286
1287 let guide = registry.migration_guide(&Version::new(0, 1, 0), &Version::new(0, 2, 0));
1288
1289 assert!(guide.contains("Removed APIs"));
1290 assert!(guide.contains("OldFeature"));
1291 assert!(guide.contains("Replacement"));
1292 assert!(guide.contains("Feature2"));
1293 assert!(guide.contains("New APIs"));
1294 assert!(guide.contains("Migration Checklist"));
1295 }
1296
1297 #[test]
1298 fn test_api_compatibility_checker() {
1299 let mut checker = ApiCompatibilityChecker::new();
1300
1301 let initial_apis = vec![create_api_signature(
1303 "test_func",
1304 "test_module",
1305 vec![Parameter {
1306 name: "param1".to_string(),
1307 type_name: "i32".to_string(),
1308 is_optional: false,
1309 defaultvalue: None,
1310 }],
1311 Some("String".to_string()),
1312 Visibility::Public,
1313 StabilityLevel::Stable,
1314 )];
1315
1316 checker.freeze_apis(initial_apis.clone()).unwrap();
1318 assert_eq!(checker.freeze_status, ApiFreezeStatus::Frozen);
1319
1320 let result = checker.check_compatibility(&initial_apis);
1322 assert!(result.is_compatible);
1323 assert!(result.violations.is_empty());
1324 }
1325
1326 #[test]
1327 fn test_api_signature_changes() {
1328 let mut checker = ApiCompatibilityChecker::for_beta1();
1329
1330 let original_api = create_api_signature(
1331 "test_func",
1332 "test_module",
1333 vec![Parameter {
1334 name: "param1".to_string(),
1335 type_name: "i32".to_string(),
1336 is_optional: false,
1337 defaultvalue: None,
1338 }],
1339 Some("String".to_string()),
1340 Visibility::Public,
1341 StabilityLevel::Stable,
1342 );
1343
1344 checker.freeze_apis(vec![original_api.clone()]).unwrap();
1345
1346 let modified_api = create_api_signature(
1348 "test_func",
1349 "test_module",
1350 vec![Parameter {
1351 name: "param1".to_string(),
1352 type_name: "f64".to_string(), is_optional: false,
1354 defaultvalue: None,
1355 }],
1356 Some("String".to_string()),
1357 Visibility::Public,
1358 StabilityLevel::Stable,
1359 );
1360
1361 let result = checker.check_compatibility(&[modified_api]);
1362 assert!(!result.is_compatible);
1363 assert!(!result.violations.is_empty());
1364 assert_eq!(
1365 result.violations[0].violation_type,
1366 CompatibilityRuleType::ParameterTypeChange
1367 );
1368 }
1369
1370 #[test]
1371 fn test_api_removal_detection() {
1372 let mut checker = ApiCompatibilityChecker::for_beta1();
1373
1374 let api1 = create_api_signature(
1375 "func1",
1376 "module",
1377 vec![],
1378 None,
1379 Visibility::Public,
1380 StabilityLevel::Stable,
1381 );
1382
1383 let api2 = create_api_signature(
1384 "func2",
1385 "module",
1386 vec![],
1387 None,
1388 Visibility::Public,
1389 StabilityLevel::Stable,
1390 );
1391
1392 checker
1393 .freeze_apis(vec![api1.clone(), api2.clone()])
1394 .unwrap();
1395
1396 let result = checker.check_compatibility(&[api1]); assert!(!result.is_compatible);
1399 assert_eq!(result.violations.len(), 1);
1400 assert_eq!(
1401 result.violations[0].violation_type,
1402 CompatibilityRuleType::PublicApiRemoval
1403 );
1404 }
1405
1406 #[test]
1407 fn test_visibility_reduction() {
1408 let mut checker = ApiCompatibilityChecker::for_beta1();
1409
1410 let public_api = create_api_signature(
1411 "func",
1412 "module",
1413 vec![],
1414 None,
1415 Visibility::Public,
1416 StabilityLevel::Stable,
1417 );
1418
1419 checker.freeze_apis(vec![public_api]).unwrap();
1420
1421 let private_api = create_api_signature(
1423 "func",
1424 "module",
1425 vec![],
1426 None,
1427 Visibility::Private, StabilityLevel::Stable,
1429 );
1430
1431 let result = checker.check_compatibility(&[private_api]);
1432 assert!(!result.is_compatible);
1433 assert_eq!(
1434 result.violations[0].violation_type,
1435 CompatibilityRuleType::VisibilityReduction
1436 );
1437 }
1438
1439 #[test]
1440 fn test_new_required_parameter() {
1441 let mut checker = ApiCompatibilityChecker::for_beta1();
1442
1443 let original_api = create_api_signature(
1444 "func",
1445 "module",
1446 vec![Parameter {
1447 name: "param1".to_string(),
1448 type_name: "i32".to_string(),
1449 is_optional: false,
1450 defaultvalue: None,
1451 }],
1452 None,
1453 Visibility::Public,
1454 StabilityLevel::Stable,
1455 );
1456
1457 checker.freeze_apis(vec![original_api]).unwrap();
1458
1459 let modified_api = create_api_signature(
1461 "func",
1462 "module",
1463 vec![
1464 Parameter {
1465 name: "param1".to_string(),
1466 type_name: "i32".to_string(),
1467 is_optional: false,
1468 defaultvalue: None,
1469 },
1470 Parameter {
1471 name: "param2".to_string(),
1472 type_name: "String".to_string(),
1473 is_optional: false, defaultvalue: None,
1475 },
1476 ],
1477 None,
1478 Visibility::Public,
1479 StabilityLevel::Stable,
1480 );
1481
1482 let result = checker.check_compatibility(&[modified_api]);
1483 assert!(!result.is_compatible);
1484 assert_eq!(
1485 result.violations[0].violation_type,
1486 CompatibilityRuleType::NewRequiredParameter
1487 );
1488 }
1489
1490 #[test]
1491 fn test_compatibility_report_generation() {
1492 let mut checker = ApiCompatibilityChecker::for_beta1();
1493
1494 let original_api = create_api_signature(
1495 "test_func",
1496 "test_module",
1497 vec![],
1498 None,
1499 Visibility::Public,
1500 StabilityLevel::Stable,
1501 );
1502
1503 checker.freeze_apis(vec![original_api]).unwrap();
1504
1505 let result = checker.check_compatibility(&[]); let report = checker.generate_compatibility_report(&result);
1508
1509 assert!(report.contains("API Compatibility Report"));
1510 assert!(report.contains("Freeze Status"));
1511 assert!(report.contains("Compatibility Violations"));
1512 assert!(report.contains("Next Steps"));
1513 assert!(report.contains("đĨ")); }
1515
1516 #[test]
1517 fn test_beta1_initialization() {
1518 let result = initialize_beta1_freeze();
1520 assert!(result.is_ok());
1521
1522 let compat_result = check_current_compatibility();
1524 assert!(compat_result.is_ok());
1525
1526 let result = compat_result.unwrap();
1527 assert!(result.is_compatible);
1529 }
1530
1531 #[test]
1532 fn test_api_signature_creation() {
1533 let signature = create_api_signature(
1534 "test_func",
1535 "test_module",
1536 vec![Parameter {
1537 name: "param".to_string(),
1538 type_name: "i32".to_string(),
1539 is_optional: false,
1540 defaultvalue: None,
1541 }],
1542 Some("String".to_string()),
1543 Visibility::Public,
1544 StabilityLevel::Stable,
1545 );
1546
1547 assert_eq!(signature.name, "test_func");
1548 assert_eq!(signature.module, "test_module");
1549 assert_eq!(signature.parameters.len(), 1);
1550 assert_eq!(signature.return_type, Some("String".to_string()));
1551 assert_eq!(signature.visibility, Visibility::Public);
1552 assert_eq!(signature.stability, StabilityLevel::Stable);
1553 assert!(signature.signature_hash != 0); }
1555}