1use super::Version;
7use crate::error::CoreError;
8use std::collections::HashMap;
9
10#[cfg(feature = "serialization")]
11use serde::{Deserialize, Serialize};
12
13#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
15#[derive(Debug, Clone)]
16pub struct DeprecationPolicy {
17 pub default_deprecation_period: u32,
19 pub grace_period: u32,
21 pub notice_period: u32,
23 pub auto_deprecation_rules: Vec<AutoDeprecationRule>,
25 pub deprecation_support_level: SupportLevel,
27 pub migration_assistance: bool,
29}
30
31impl Default for DeprecationPolicy {
32 fn default() -> Self {
33 Self {
34 default_deprecation_period: 365, grace_period: 90, notice_period: 180, auto_deprecation_rules: vec![
38 AutoDeprecationRule::MajorVersionSuperseded {
39 versions_to_keep: 2,
40 },
41 AutoDeprecationRule::AgeBasedDeprecation { maxage_days: 1095 }, ],
43 deprecation_support_level: SupportLevel::SecurityOnly,
44 migration_assistance: true,
45 }
46 }
47}
48
49#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum SupportLevel {
53 Full,
55 MaintenanceOnly,
57 SecurityOnly,
59 None,
61}
62
63#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
65#[derive(Debug, Clone)]
66pub enum AutoDeprecationRule {
67 MajorVersionSuperseded { versions_to_keep: u32 },
69 AgeBasedDeprecation { maxage_days: u32 },
71 UsageBasedDeprecation { min_usage_percent: f64 },
73 StableVersionReleased,
75}
76
77#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
79#[derive(Debug, Clone)]
80pub struct DeprecationStatus {
81 pub version: Version,
83 pub phase: DeprecationPhase,
85 pub announced_date: chrono::DateTime<chrono::Utc>,
87 pub end_of_life_date: chrono::DateTime<chrono::Utc>,
89 pub actual_end_of_life: Option<chrono::DateTime<chrono::Utc>>,
91 pub reason: DeprecationReason,
93 pub replacement_version: Option<Version>,
95 pub migration_guide: Option<String>,
97 pub support_level: SupportLevel,
99 pub usage_metrics: Option<UsageMetrics>,
101}
102
103#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
105#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
106pub enum DeprecationPhase {
107 Active,
109 Announced,
111 Deprecated,
113 EndOfLife,
115 Removed,
117}
118
119impl DeprecationPhase {
120 pub const fn as_str(&self) -> &'static str {
122 match self {
123 DeprecationPhase::Active => "active",
124 DeprecationPhase::Announced => "announced",
125 DeprecationPhase::Deprecated => "deprecated",
126 DeprecationPhase::EndOfLife => "end_of_life",
127 DeprecationPhase::Removed => "removed",
128 }
129 }
130
131 pub fn is_supported(&self) -> bool {
133 matches!(
134 self,
135 DeprecationPhase::Active | DeprecationPhase::Announced | DeprecationPhase::Deprecated
136 )
137 }
138}
139
140#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
142#[derive(Debug, Clone)]
143pub enum DeprecationReason {
144 SupersededBy(Version),
146 SecurityConcerns,
148 PerformanceIssues,
150 MaintenanceBurden,
152 LowUsage,
154 TechnologyObsolescence,
156 BusinessDecision(String),
158 VendorEndOfSupport,
160}
161
162#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
164#[derive(Debug, Clone)]
165pub struct UsageMetrics {
166 pub active_users: u64,
168 pub usage_percentage: f64,
170 pub download_count: u64,
172 pub last_usage: chrono::DateTime<chrono::Utc>,
174 pub trend: UsageTrend,
176}
177
178#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum UsageTrend {
182 Increasing,
184 Stable,
186 Decreasing,
188 Declining,
190}
191
192#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
194#[derive(Debug, Clone)]
195pub struct DeprecationAnnouncement {
196 pub version: Version,
198 pub announcement_date: chrono::DateTime<chrono::Utc>,
200 pub timeline: DeprecationTimeline,
202 pub message: String,
204 pub migration_instructions: Option<String>,
206 pub support_contact: Option<String>,
208 pub communication_channels: Vec<CommunicationChannel>,
210}
211
212#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
214#[derive(Debug, Clone)]
215pub struct DeprecationTimeline {
216 pub announced: chrono::DateTime<chrono::Utc>,
218 pub deprecated_date: chrono::DateTime<chrono::Utc>,
220 pub end_of_life: chrono::DateTime<chrono::Utc>,
222 pub removal_date: Option<chrono::DateTime<chrono::Utc>>,
224 pub milestones: Vec<DeprecationMilestone>,
226}
227
228#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
230#[derive(Debug, Clone)]
231pub struct DeprecationMilestone {
232 pub date: chrono::DateTime<chrono::Utc>,
234 pub description: String,
236 pub actions: Vec<String>,
238}
239
240#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
242#[derive(Debug, Clone)]
243pub enum CommunicationChannel {
244 Email,
246 Website,
248 ApiHeaders,
250 Documentation,
252 BlogPost,
254 Newsletter,
256 SocialMedia,
258 DirectNotification,
260}
261
262fn deprecation_timeline(
264 version: &Version,
265 _replacement_version: Option<&Version>,
266) -> DeprecationTimeline {
267 let now = chrono::Utc::now();
268 let deprecated_date = now + chrono::Duration::days(30);
269 let end_of_life = now + chrono::Duration::days(180);
270 let removal_date = now + chrono::Duration::days(365);
271
272 let milestones = vec![
273 DeprecationMilestone {
274 date: deprecated_date,
275 description: format!("Version {version} will be deprecated"),
276 actions: vec!["Update to newer version".to_string()],
277 },
278 DeprecationMilestone {
279 date: end_of_life,
280 description: format!("Version {version} reaches end of life"),
281 actions: vec!["Support will be discontinued".to_string()],
282 },
283 ];
284
285 DeprecationTimeline {
286 announced: now,
287 deprecated_date,
288 end_of_life,
289 removal_date: Some(removal_date),
290 milestones,
291 }
292}
293
294pub struct DeprecationManager {
296 policy: DeprecationPolicy,
298 deprecations: HashMap<Version, DeprecationStatus>,
300 announcements: Vec<DeprecationAnnouncement>,
302}
303
304impl DeprecationManager {
305 pub fn new() -> Self {
307 Self {
308 policy: DeprecationPolicy::default(),
309 deprecations: HashMap::new(),
310 announcements: Vec::new(),
311 }
312 }
313
314 pub fn with_policy(policy: DeprecationPolicy) -> Self {
316 Self {
317 policy,
318 deprecations: HashMap::new(),
319 announcements: Vec::new(),
320 }
321 }
322
323 pub fn register_version(&mut self, apiversion: &super::ApiVersion) -> Result<(), CoreError> {
325 let status = DeprecationStatus {
327 version: apiversion.version.clone(),
328 phase: DeprecationPhase::Active,
329 announced_date: apiversion.release_date,
330 end_of_life_date: apiversion.end_of_life.unwrap_or_else(|| {
331 apiversion.release_date
332 + chrono::Duration::days(self.policy.default_deprecation_period as i64)
333 }),
334 actual_end_of_life: None,
335 reason: DeprecationReason::BusinessDecision("Not deprecated".to_string()),
336 replacement_version: None,
337 migration_guide: None,
338 support_level: SupportLevel::Full,
339 usage_metrics: None,
340 };
341
342 self.deprecations.insert(apiversion.version.clone(), status);
343 Ok(())
344 }
345
346 pub fn announce_deprecation(
348 &mut self,
349 version: &Version,
350 reason: DeprecationReason,
351 replacement_version: Option<Version>,
352 ) -> Result<DeprecationAnnouncement, CoreError> {
353 let status = self.deprecations.get_mut(version).ok_or_else(|| {
354 CoreError::ComputationError(crate::error::ErrorContext::new(format!(
355 "Version {version} not registered"
356 )))
357 })?;
358
359 let now = chrono::Utc::now();
360 let deprecated_date = now + chrono::Duration::days(self.policy.notice_period as i64);
361 let end_of_life =
362 deprecated_date + chrono::Duration::days(self.policy.default_deprecation_period as i64);
363
364 status.phase = DeprecationPhase::Announced;
366 status.announced_date = now;
367 status.end_of_life_date = end_of_life;
368 status.reason = reason.clone();
369 status.replacement_version = replacement_version.clone();
370 status.support_level = self.policy.deprecation_support_level;
371
372 let timeline = DeprecationTimeline {
374 announced: now,
375 deprecated_date,
376 end_of_life,
377 removal_date: Some(
378 end_of_life + chrono::Duration::days(self.policy.grace_period as i64),
379 ),
380 milestones: vec![
381 DeprecationMilestone {
382 date: deprecated_date,
383 description: "Version enters deprecated phase".to_string(),
384 actions: vec!["Migration recommended".to_string()],
385 },
386 DeprecationMilestone {
387 date: end_of_life - chrono::Duration::days(30),
388 description: "Final warning - 30 days to end of life".to_string(),
389 actions: vec!["Complete migration immediately".to_string()],
390 },
391 ],
392 };
393
394 let announcement = DeprecationAnnouncement {
396 version: version.clone(),
397 announcement_date: now,
398 timeline,
399 message: self.generate_deprecation_message(
400 version,
401 &reason,
402 replacement_version.as_ref(),
403 ),
404 migration_instructions: self
405 .generate_migration_instructions(version, replacement_version.as_ref()),
406 support_contact: Some("support@scirs.dev".to_string()),
407 communication_channels: vec![
408 CommunicationChannel::Email,
409 CommunicationChannel::Website,
410 CommunicationChannel::ApiHeaders,
411 CommunicationChannel::Documentation,
412 ],
413 };
414
415 self.announcements.push(announcement.clone());
416 Ok(announcement)
417 }
418
419 pub fn get_deprecation_status(&self, version: &Version) -> Option<DeprecationStatus> {
421 self.deprecations.get(version).cloned()
422 }
423
424 pub fn update_status(
426 &mut self,
427 version: &Version,
428 new_status: DeprecationStatus,
429 ) -> Result<(), CoreError> {
430 if !self.deprecations.contains_key(version) {
431 return Err(CoreError::ComputationError(
432 crate::error::ErrorContext::new(format!("Version {version} not registered")),
433 ));
434 }
435
436 self.deprecations.insert(version.clone(), new_status);
437 Ok(())
438 }
439
440 pub fn perform_maintenance(&mut self) -> Result<Vec<MaintenanceAction>, CoreError> {
442 let mut actions = Vec::new();
443 let now = chrono::Utc::now();
444
445 for rule in &self.policy.auto_deprecation_rules.clone() {
447 actions.extend(self.apply_auto_deprecation_rule(rule, now)?);
448 }
449
450 for (version, status) in &mut self.deprecations {
452 let old_phase = status.phase;
453
454 if status.phase == DeprecationPhase::Announced
455 && now
456 >= status.end_of_life_date
457 - chrono::Duration::days(self.policy.default_deprecation_period as i64)
458 {
459 status.phase = DeprecationPhase::Deprecated;
460 actions.push(MaintenanceAction::PhaseTransition {
461 version: version.clone(),
462 from_phase: old_phase,
463 to_phase: status.phase,
464 });
465 }
466
467 if status.phase == DeprecationPhase::Deprecated && now >= status.end_of_life_date {
468 status.phase = DeprecationPhase::EndOfLife;
469 status.actual_end_of_life = Some(now);
470 actions.push(MaintenanceAction::PhaseTransition {
471 version: version.clone(),
472 from_phase: old_phase,
473 to_phase: status.phase,
474 });
475 }
476 }
477
478 Ok(actions)
479 }
480
481 fn apply_auto_deprecation_rule(
483 &mut self,
484 rule: &AutoDeprecationRule,
485 now: chrono::DateTime<chrono::Utc>,
486 ) -> Result<Vec<MaintenanceAction>, CoreError> {
487 let mut actions = Vec::new();
488
489 match rule {
490 AutoDeprecationRule::MajorVersionSuperseded { versions_to_keep } => {
491 actions.extend(self.apply_major_version_rule(*versions_to_keep)?);
492 }
493 AutoDeprecationRule::AgeBasedDeprecation { maxage_days } => {
494 actions.extend(self.apply_agebased_rule(*maxage_days, now)?);
495 }
496 AutoDeprecationRule::UsageBasedDeprecation { min_usage_percent } => {
497 actions.extend(self.apply_usagebased_rule(*min_usage_percent)?);
498 }
499 AutoDeprecationRule::StableVersionReleased => {
500 actions.extend(self.apply_stable_release_rule()?);
501 }
502 }
503
504 Ok(actions)
505 }
506
507 fn apply_major_version_rule(
509 &self,
510 versions_to_keep: u32,
511 ) -> Result<Vec<MaintenanceAction>, CoreError> {
512 let mut actions = Vec::new();
513
514 let mut majorversions: std::collections::BTreeMap<u64, Vec<Version>> =
516 std::collections::BTreeMap::new();
517 for version in self.deprecations.keys() {
518 majorversions
519 .entry(version.major())
520 .or_default()
521 .push(version.clone());
522 }
523
524 let major_keys: Vec<u64> = majorversions.keys().cloned().collect();
526 if major_keys.len() > versions_to_keep as usize {
527 let to_deprecate = &major_keys[..major_keys.len() - versions_to_keep as usize];
528
529 for &major in to_deprecate {
530 if let Some(versions) = majorversions.get(&major) {
531 for version in versions {
532 if let Some(status) = self.deprecations.get(version) {
533 if status.phase == DeprecationPhase::Active {
534 let latest_major = major_keys.last().unwrap();
535 let replacement = Version::new(*latest_major, 0, 0);
536
537 let _announcement = DeprecationAnnouncement {
538 version: version.clone(),
539 announcement_date: chrono::Utc::now(),
540 timeline: deprecation_timeline(version, Some(&replacement)),
541 message: format!(
542 "Version {version} is deprecated in favor of {replacement}"
543 ),
544 migration_instructions: Some(format!(
545 "Please migrate to version {replacement}"
546 )),
547 support_contact: None,
548 communication_channels: vec![],
549 };
550
551 actions.push(MaintenanceAction::AutoDeprecation {
552 version: version.clone(),
553 rule: format!(
554 "Major version superseded (keep {versions_to_keep})"
555 ),
556 });
557 }
558 }
559 }
560 }
561 }
562 }
563
564 Ok(actions)
565 }
566
567 fn apply_agebased_rule(
569 &self,
570 maxage_days: u32,
571 now: chrono::DateTime<chrono::Utc>,
572 ) -> Result<Vec<MaintenanceAction>, CoreError> {
573 let mut actions = Vec::new();
574 let maxage = chrono::Duration::days(maxage_days as i64);
575
576 for (version, status) in &self.deprecations.clone() {
577 if status.phase == DeprecationPhase::Active {
578 let age = now.signed_duration_since(status.announced_date);
579 if age > maxage {
580 let _announcement = DeprecationAnnouncement {
581 version: version.clone(),
582 timeline: deprecation_timeline(version, None),
583 announcement_date: now,
584 message: format!("Version {version} deprecated due to maintenance burden"),
585 migration_instructions: Some("Please upgrade to newer version".to_string()),
586 support_contact: Some("support@scirs.org".to_string()),
587 communication_channels: vec![
588 CommunicationChannel::Documentation,
589 CommunicationChannel::Email,
590 ],
591 };
592
593 actions.push(MaintenanceAction::AutoDeprecation {
594 version: version.clone(),
595 rule: format!("Age-based deprecation (max {maxage_days} days)"),
596 });
597 }
598 }
599 }
600
601 Ok(actions)
602 }
603
604 fn apply_usagebased_rule(
606 &self,
607 _min_usage_percent: f64,
608 ) -> Result<Vec<MaintenanceAction>, CoreError> {
609 Ok(Vec::new())
612 }
613
614 fn apply_stable_release_rule(&self) -> Result<Vec<MaintenanceAction>, CoreError> {
616 Ok(Vec::new())
619 }
620
621 fn generate_deprecation_message(
623 &self,
624 version: &Version,
625 reason: &DeprecationReason,
626 replacement: Option<&Version>,
627 ) -> String {
628 let reason_str = match reason {
629 DeprecationReason::SupersededBy(v) => format!("{v}"),
630 DeprecationReason::SecurityConcerns => "security concerns".to_string(),
631 DeprecationReason::PerformanceIssues => "performance issues".to_string(),
632 DeprecationReason::MaintenanceBurden => "maintenance burden".to_string(),
633 DeprecationReason::LowUsage => "low usage".to_string(),
634 DeprecationReason::TechnologyObsolescence => "technology obsolescence".to_string(),
635 DeprecationReason::BusinessDecision(msg) => msg.clone(),
636 DeprecationReason::VendorEndOfSupport => "vendor end of support".to_string(),
637 };
638
639 let mut message = format!("Version {version} has been deprecated due to {reason_str}. ");
640
641 if let Some(replacement) = replacement {
642 message.push_str(&format!(
643 "Please migrate to version {replacement} as soon as possible. "
644 ));
645 }
646
647 message.push_str(&format!(
648 "Support will end on {}. ",
649 self.deprecations
650 .get(version)
651 .map(|s| s.end_of_life_date.format("%Y-%m-%d").to_string())
652 .unwrap_or_else(|| "TBD".to_string())
653 ));
654
655 message
656 }
657
658 fn generate_migration_instructions(
660 &self,
661 _current_version: &Version,
662 replacement: Option<&Version>,
663 ) -> Option<String> {
664 replacement.map(|replacement| {
665 format!(
666 "To migrate to version {replacement}:\n\
667 1. Update your dependency to version {replacement}\n\
668 2. Review the changelog for breaking changes\n\
669 3. Update your code as necessary\n\
670 4. Test thoroughly before deploying\n\
671 5. Contact support if you need assistance"
672 )
673 })
674 }
675
676 pub fn get_deprecatedversions(&self) -> Vec<&DeprecationStatus> {
678 self.deprecations
679 .values()
680 .filter(|status| status.phase != DeprecationPhase::Active)
681 .collect()
682 }
683
684 pub fn getversions_in_phase(&self, phase: DeprecationPhase) -> Vec<&DeprecationStatus> {
686 self.deprecations
687 .values()
688 .filter(|status| status.phase == phase)
689 .collect()
690 }
691}
692
693impl Default for DeprecationManager {
694 fn default() -> Self {
695 Self::new()
696 }
697}
698
699#[derive(Debug, Clone)]
701pub enum MaintenanceAction {
702 PhaseTransition {
704 version: Version,
705 from_phase: DeprecationPhase,
706 to_phase: DeprecationPhase,
707 },
708 AutoDeprecation { version: Version, rule: String },
710 UsageMetricsUpdated { version: Version },
712}
713
714#[cfg(test)]
715mod tests {
716 use super::*;
717
718 #[test]
719 fn test_deprecation_manager_creation() {
720 let manager = DeprecationManager::new();
721 assert!(manager.deprecations.is_empty());
722 assert!(manager.announcements.is_empty());
723 }
724
725 #[test]
726 fn test_deprecation_phases() {
727 assert!(DeprecationPhase::Active < DeprecationPhase::Deprecated);
728 assert!(DeprecationPhase::Deprecated < DeprecationPhase::EndOfLife);
729 assert!(DeprecationPhase::Active.is_supported());
730 assert!(!DeprecationPhase::EndOfLife.is_supported());
731 }
732
733 #[test]
734 fn test_deprecation_announcement() {
735 let mut manager = DeprecationManager::new();
736 let version = Version::new(1, 0, 0);
737
738 let apiversion = super::super::ApiVersionBuilder::new(version.clone())
740 .build()
741 .unwrap();
742 manager.register_version(&apiversion).unwrap();
743
744 let replacement = Version::new(2, 0, 0);
746 let announcement = manager
747 .announce_deprecation(
748 &version,
749 DeprecationReason::SupersededBy(replacement.clone()),
750 Some(replacement),
751 )
752 .unwrap();
753
754 assert_eq!(announcement.version, version);
755 assert!(!announcement.message.is_empty());
756 assert!(announcement.migration_instructions.is_some());
757
758 let status = manager.get_deprecation_status(&version).unwrap();
760 assert_eq!(status.phase, DeprecationPhase::Announced);
761 }
762
763 #[test]
764 fn test_deprecation_policy() {
765 let policy = DeprecationPolicy::default();
766 assert_eq!(policy.default_deprecation_period, 365);
767 assert_eq!(policy.grace_period, 90);
768 assert_eq!(policy.notice_period, 180);
769 assert!(policy.migration_assistance);
770 }
771
772 #[test]
773 fn test_auto_deprecation_rules() {
774 let mut manager = DeprecationManager::new();
775
776 for major in 1..=5 {
778 let version = Version::new(major, 0, 0);
779 let apiversion = super::super::ApiVersionBuilder::new(version)
780 .build()
781 .unwrap();
782 manager.register_version(&apiversion).unwrap();
783 }
784
785 let rule = AutoDeprecationRule::MajorVersionSuperseded {
787 versions_to_keep: 2,
788 };
789 let actions = manager
790 .apply_auto_deprecation_rule(&rule, chrono::Utc::now())
791 .unwrap();
792
793 assert!(!actions.is_empty());
795 }
796
797 #[test]
798 fn test_usage_trends() {
799 let metrics = UsageMetrics {
800 active_users: 100,
801 usage_percentage: 5.0,
802 download_count: 1000,
803 last_usage: chrono::Utc::now(),
804 trend: UsageTrend::Decreasing,
805 };
806
807 assert_eq!(metrics.trend, UsageTrend::Decreasing);
808 assert_eq!(metrics.usage_percentage, 5.0);
809 }
810}