use super::Version;
use crate::error::CoreError;
use std::collections::HashMap;
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DeprecationPolicy {
pub default_deprecation_period: u32,
pub grace_period: u32,
pub notice_period: u32,
pub auto_deprecation_rules: Vec<AutoDeprecationRule>,
pub deprecation_support_level: SupportLevel,
pub migration_assistance: bool,
}
impl Default for DeprecationPolicy {
fn default() -> Self {
Self {
default_deprecation_period: 365, grace_period: 90, notice_period: 180, auto_deprecation_rules: vec![
AutoDeprecationRule::MajorVersionSuperseded {
versions_to_keep: 2,
},
AutoDeprecationRule::AgeBasedDeprecation { maxage_days: 1095 }, ],
deprecation_support_level: SupportLevel::SecurityOnly,
migration_assistance: true,
}
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SupportLevel {
Full,
MaintenanceOnly,
SecurityOnly,
None,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub enum AutoDeprecationRule {
MajorVersionSuperseded { versions_to_keep: u32 },
AgeBasedDeprecation { maxage_days: u32 },
UsageBasedDeprecation { min_usage_percent: f64 },
StableVersionReleased,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DeprecationStatus {
pub version: Version,
pub phase: DeprecationPhase,
pub announced_date: chrono::DateTime<chrono::Utc>,
pub end_of_life_date: chrono::DateTime<chrono::Utc>,
pub actual_end_of_life: Option<chrono::DateTime<chrono::Utc>>,
pub reason: DeprecationReason,
pub replacement_version: Option<Version>,
pub migration_guide: Option<String>,
pub support_level: SupportLevel,
pub usage_metrics: Option<UsageMetrics>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DeprecationPhase {
Active,
Announced,
Deprecated,
EndOfLife,
Removed,
}
impl DeprecationPhase {
pub const fn as_str(&self) -> &'static str {
match self {
DeprecationPhase::Active => "active",
DeprecationPhase::Announced => "announced",
DeprecationPhase::Deprecated => "deprecated",
DeprecationPhase::EndOfLife => "end_of_life",
DeprecationPhase::Removed => "removed",
}
}
pub fn is_supported(&self) -> bool {
matches!(
self,
DeprecationPhase::Active | DeprecationPhase::Announced | DeprecationPhase::Deprecated
)
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub enum DeprecationReason {
SupersededBy(Version),
SecurityConcerns,
PerformanceIssues,
MaintenanceBurden,
LowUsage,
TechnologyObsolescence,
BusinessDecision(String),
VendorEndOfSupport,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct UsageMetrics {
pub active_users: u64,
pub usage_percentage: f64,
pub download_count: u64,
pub last_usage: chrono::DateTime<chrono::Utc>,
pub trend: UsageTrend,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UsageTrend {
Increasing,
Stable,
Decreasing,
Declining,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DeprecationAnnouncement {
pub version: Version,
pub announcement_date: chrono::DateTime<chrono::Utc>,
pub timeline: DeprecationTimeline,
pub message: String,
pub migration_instructions: Option<String>,
pub support_contact: Option<String>,
pub communication_channels: Vec<CommunicationChannel>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DeprecationTimeline {
pub announced: chrono::DateTime<chrono::Utc>,
pub deprecated_date: chrono::DateTime<chrono::Utc>,
pub end_of_life: chrono::DateTime<chrono::Utc>,
pub removal_date: Option<chrono::DateTime<chrono::Utc>>,
pub milestones: Vec<DeprecationMilestone>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DeprecationMilestone {
pub date: chrono::DateTime<chrono::Utc>,
pub description: String,
pub actions: Vec<String>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub enum CommunicationChannel {
Email,
Website,
ApiHeaders,
Documentation,
BlogPost,
Newsletter,
SocialMedia,
DirectNotification,
}
fn deprecation_timeline(
version: &Version,
_replacement_version: Option<&Version>,
) -> DeprecationTimeline {
let now = chrono::Utc::now();
let deprecated_date = now + chrono::Duration::days(30);
let end_of_life = now + chrono::Duration::days(180);
let removal_date = now + chrono::Duration::days(365);
let milestones = vec![
DeprecationMilestone {
date: deprecated_date,
description: format!("Version {version} will be deprecated"),
actions: vec!["Update to newer version".to_string()],
},
DeprecationMilestone {
date: end_of_life,
description: format!("Version {version} reaches end of life"),
actions: vec!["Support will be discontinued".to_string()],
},
];
DeprecationTimeline {
announced: now,
deprecated_date,
end_of_life,
removal_date: Some(removal_date),
milestones,
}
}
pub struct DeprecationManager {
policy: DeprecationPolicy,
deprecations: HashMap<Version, DeprecationStatus>,
announcements: Vec<DeprecationAnnouncement>,
}
impl DeprecationManager {
pub fn new() -> Self {
Self {
policy: DeprecationPolicy::default(),
deprecations: HashMap::new(),
announcements: Vec::new(),
}
}
pub fn with_policy(policy: DeprecationPolicy) -> Self {
Self {
policy,
deprecations: HashMap::new(),
announcements: Vec::new(),
}
}
pub fn register_version(&mut self, apiversion: &super::ApiVersion) -> Result<(), CoreError> {
let status = DeprecationStatus {
version: apiversion.version.clone(),
phase: DeprecationPhase::Active,
announced_date: apiversion.release_date,
end_of_life_date: apiversion.end_of_life.unwrap_or_else(|| {
apiversion.release_date
+ chrono::Duration::days(self.policy.default_deprecation_period as i64)
}),
actual_end_of_life: None,
reason: DeprecationReason::BusinessDecision("Not deprecated".to_string()),
replacement_version: None,
migration_guide: None,
support_level: SupportLevel::Full,
usage_metrics: None,
};
self.deprecations.insert(apiversion.version.clone(), status);
Ok(())
}
pub fn announce_deprecation(
&mut self,
version: &Version,
reason: DeprecationReason,
replacement_version: Option<Version>,
) -> Result<DeprecationAnnouncement, CoreError> {
let status = self.deprecations.get_mut(version).ok_or_else(|| {
CoreError::ComputationError(crate::error::ErrorContext::new(format!(
"Version {version} not registered"
)))
})?;
let now = chrono::Utc::now();
let deprecated_date = now + chrono::Duration::days(self.policy.notice_period as i64);
let end_of_life =
deprecated_date + chrono::Duration::days(self.policy.default_deprecation_period as i64);
status.phase = DeprecationPhase::Announced;
status.announced_date = now;
status.end_of_life_date = end_of_life;
status.reason = reason.clone();
status.replacement_version = replacement_version.clone();
status.support_level = self.policy.deprecation_support_level;
let timeline = DeprecationTimeline {
announced: now,
deprecated_date,
end_of_life,
removal_date: Some(
end_of_life + chrono::Duration::days(self.policy.grace_period as i64),
),
milestones: vec![
DeprecationMilestone {
date: deprecated_date,
description: "Version enters deprecated phase".to_string(),
actions: vec!["Migration recommended".to_string()],
},
DeprecationMilestone {
date: end_of_life - chrono::Duration::days(30),
description: "Final warning - 30 days to end of life".to_string(),
actions: vec!["Complete migration immediately".to_string()],
},
],
};
let announcement = DeprecationAnnouncement {
version: version.clone(),
announcement_date: now,
timeline,
message: self.generate_deprecation_message(
version,
&reason,
replacement_version.as_ref(),
),
migration_instructions: self
.generate_migration_instructions(version, replacement_version.as_ref()),
support_contact: Some("support@scirs.dev".to_string()),
communication_channels: vec![
CommunicationChannel::Email,
CommunicationChannel::Website,
CommunicationChannel::ApiHeaders,
CommunicationChannel::Documentation,
],
};
self.announcements.push(announcement.clone());
Ok(announcement)
}
pub fn get_deprecation_status(&self, version: &Version) -> Option<DeprecationStatus> {
self.deprecations.get(version).cloned()
}
pub fn update_status(
&mut self,
version: &Version,
new_status: DeprecationStatus,
) -> Result<(), CoreError> {
if !self.deprecations.contains_key(version) {
return Err(CoreError::ComputationError(
crate::error::ErrorContext::new(format!("Version {version} not registered")),
));
}
self.deprecations.insert(version.clone(), new_status);
Ok(())
}
pub fn perform_maintenance(&mut self) -> Result<Vec<MaintenanceAction>, CoreError> {
let mut actions = Vec::new();
let now = chrono::Utc::now();
for rule in &self.policy.auto_deprecation_rules.clone() {
actions.extend(self.apply_auto_deprecation_rule(rule, now)?);
}
for (version, status) in &mut self.deprecations {
let old_phase = status.phase;
if status.phase == DeprecationPhase::Announced
&& now
>= status.end_of_life_date
- chrono::Duration::days(self.policy.default_deprecation_period as i64)
{
status.phase = DeprecationPhase::Deprecated;
actions.push(MaintenanceAction::PhaseTransition {
version: version.clone(),
from_phase: old_phase,
to_phase: status.phase,
});
}
if status.phase == DeprecationPhase::Deprecated && now >= status.end_of_life_date {
status.phase = DeprecationPhase::EndOfLife;
status.actual_end_of_life = Some(now);
actions.push(MaintenanceAction::PhaseTransition {
version: version.clone(),
from_phase: old_phase,
to_phase: status.phase,
});
}
}
Ok(actions)
}
fn apply_auto_deprecation_rule(
&mut self,
rule: &AutoDeprecationRule,
now: chrono::DateTime<chrono::Utc>,
) -> Result<Vec<MaintenanceAction>, CoreError> {
let mut actions = Vec::new();
match rule {
AutoDeprecationRule::MajorVersionSuperseded { versions_to_keep } => {
actions.extend(self.apply_major_version_rule(*versions_to_keep)?);
}
AutoDeprecationRule::AgeBasedDeprecation { maxage_days } => {
actions.extend(self.apply_agebased_rule(*maxage_days, now)?);
}
AutoDeprecationRule::UsageBasedDeprecation { min_usage_percent } => {
actions.extend(self.apply_usagebased_rule(*min_usage_percent)?);
}
AutoDeprecationRule::StableVersionReleased => {
actions.extend(self.apply_stable_release_rule()?);
}
}
Ok(actions)
}
fn apply_major_version_rule(
&self,
versions_to_keep: u32,
) -> Result<Vec<MaintenanceAction>, CoreError> {
let mut actions = Vec::new();
let mut majorversions: std::collections::BTreeMap<u64, Vec<Version>> =
std::collections::BTreeMap::new();
for version in self.deprecations.keys() {
majorversions
.entry(version.major())
.or_default()
.push(version.clone());
}
let major_keys: Vec<u64> = majorversions.keys().cloned().collect();
if major_keys.len() > versions_to_keep as usize {
let to_deprecate = &major_keys[..major_keys.len() - versions_to_keep as usize];
for &major in to_deprecate {
if let Some(versions) = majorversions.get(&major) {
for version in versions {
if let Some(status) = self.deprecations.get(version) {
if status.phase == DeprecationPhase::Active {
let latest_major = major_keys.last().expect("Operation failed");
let replacement = Version::new(*latest_major, 0, 0);
let _announcement = DeprecationAnnouncement {
version: version.clone(),
announcement_date: chrono::Utc::now(),
timeline: deprecation_timeline(version, Some(&replacement)),
message: format!(
"Version {version} is deprecated in favor of {replacement}"
),
migration_instructions: Some(format!(
"Please migrate to version {replacement}"
)),
support_contact: None,
communication_channels: vec![],
};
actions.push(MaintenanceAction::AutoDeprecation {
version: version.clone(),
rule: format!(
"Major version superseded (keep {versions_to_keep})"
),
});
}
}
}
}
}
}
Ok(actions)
}
fn apply_agebased_rule(
&self,
maxage_days: u32,
now: chrono::DateTime<chrono::Utc>,
) -> Result<Vec<MaintenanceAction>, CoreError> {
let mut actions = Vec::new();
let maxage = chrono::Duration::days(maxage_days as i64);
for (version, status) in &self.deprecations.clone() {
if status.phase == DeprecationPhase::Active {
let age = now.signed_duration_since(status.announced_date);
if age > maxage {
let _announcement = DeprecationAnnouncement {
version: version.clone(),
timeline: deprecation_timeline(version, None),
announcement_date: now,
message: format!("Version {version} deprecated due to maintenance burden"),
migration_instructions: Some("Please upgrade to newer version".to_string()),
support_contact: Some("support@scirs.org".to_string()),
communication_channels: vec![
CommunicationChannel::Documentation,
CommunicationChannel::Email,
],
};
actions.push(MaintenanceAction::AutoDeprecation {
version: version.clone(),
rule: format!("Age-based deprecation (max {maxage_days} days)"),
});
}
}
}
Ok(actions)
}
fn apply_usagebased_rule(
&self,
_min_usage_percent: f64,
) -> Result<Vec<MaintenanceAction>, CoreError> {
Ok(Vec::new())
}
fn apply_stable_release_rule(&self) -> Result<Vec<MaintenanceAction>, CoreError> {
Ok(Vec::new())
}
fn generate_deprecation_message(
&self,
version: &Version,
reason: &DeprecationReason,
replacement: Option<&Version>,
) -> String {
let reason_str = match reason {
DeprecationReason::SupersededBy(v) => format!("{v}"),
DeprecationReason::SecurityConcerns => "security concerns".to_string(),
DeprecationReason::PerformanceIssues => "performance issues".to_string(),
DeprecationReason::MaintenanceBurden => "maintenance burden".to_string(),
DeprecationReason::LowUsage => "low usage".to_string(),
DeprecationReason::TechnologyObsolescence => "technology obsolescence".to_string(),
DeprecationReason::BusinessDecision(msg) => msg.clone(),
DeprecationReason::VendorEndOfSupport => "vendor end of support".to_string(),
};
let mut message = format!("Version {version} has been deprecated due to {reason_str}. ");
if let Some(replacement) = replacement {
message.push_str(&format!(
"Please migrate to version {replacement} as soon as possible. "
));
}
message.push_str(&format!(
"Support will end on {}. ",
self.deprecations
.get(version)
.map(|s| s.end_of_life_date.format("%Y-%m-%d").to_string())
.unwrap_or_else(|| "TBD".to_string())
));
message
}
fn generate_migration_instructions(
&self,
_current_version: &Version,
replacement: Option<&Version>,
) -> Option<String> {
replacement.map(|replacement| {
format!(
"To migrate to version {replacement}:\n\
1. Update your dependency to version {replacement}\n\
2. Review the changelog for breaking changes\n\
3. Update your code as necessary\n\
4. Test thoroughly before deploying\n\
5. Contact support if you need assistance"
)
})
}
pub fn get_deprecatedversions(&self) -> Vec<&DeprecationStatus> {
self.deprecations
.values()
.filter(|status| status.phase != DeprecationPhase::Active)
.collect()
}
pub fn getversions_in_phase(&self, phase: DeprecationPhase) -> Vec<&DeprecationStatus> {
self.deprecations
.values()
.filter(|status| status.phase == phase)
.collect()
}
}
impl Default for DeprecationManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum MaintenanceAction {
PhaseTransition {
version: Version,
from_phase: DeprecationPhase,
to_phase: DeprecationPhase,
},
AutoDeprecation { version: Version, rule: String },
UsageMetricsUpdated { version: Version },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deprecation_manager_creation() {
let manager = DeprecationManager::new();
assert!(manager.deprecations.is_empty());
assert!(manager.announcements.is_empty());
}
#[test]
fn test_deprecation_phases() {
assert!(DeprecationPhase::Active < DeprecationPhase::Deprecated);
assert!(DeprecationPhase::Deprecated < DeprecationPhase::EndOfLife);
assert!(DeprecationPhase::Active.is_supported());
assert!(!DeprecationPhase::EndOfLife.is_supported());
}
#[test]
fn test_deprecation_announcement() {
let mut manager = DeprecationManager::new();
let version = Version::new(1, 0, 0);
let apiversion = super::super::ApiVersionBuilder::new(version.clone())
.build()
.expect("Operation failed");
manager
.register_version(&apiversion)
.expect("Operation failed");
let replacement = Version::new(2, 0, 0);
let announcement = manager
.announce_deprecation(
&version,
DeprecationReason::SupersededBy(replacement.clone()),
Some(replacement),
)
.expect("Operation failed");
assert_eq!(announcement.version, version);
assert!(!announcement.message.is_empty());
assert!(announcement.migration_instructions.is_some());
let status = manager
.get_deprecation_status(&version)
.expect("Operation failed");
assert_eq!(status.phase, DeprecationPhase::Announced);
}
#[test]
fn test_deprecation_policy() {
let policy = DeprecationPolicy::default();
assert_eq!(policy.default_deprecation_period, 365);
assert_eq!(policy.grace_period, 90);
assert_eq!(policy.notice_period, 180);
assert!(policy.migration_assistance);
}
#[test]
fn test_auto_deprecation_rules() {
let mut manager = DeprecationManager::new();
for major in 1..=5 {
let version = Version::new(major, 0, 0);
let apiversion = super::super::ApiVersionBuilder::new(version)
.build()
.expect("Operation failed");
manager
.register_version(&apiversion)
.expect("Operation failed");
}
let rule = AutoDeprecationRule::MajorVersionSuperseded {
versions_to_keep: 2,
};
let actions = manager
.apply_auto_deprecation_rule(&rule, chrono::Utc::now())
.expect("Operation failed");
assert!(!actions.is_empty());
}
#[test]
fn test_usage_trends() {
let metrics = UsageMetrics {
active_users: 100,
usage_percentage: 5.0,
download_count: 1000,
last_usage: chrono::Utc::now(),
trend: UsageTrend::Decreasing,
};
assert_eq!(metrics.trend, UsageTrend::Decreasing);
assert_eq!(metrics.usage_percentage, 5.0);
}
}