pub mod compatibility;
pub mod deprecation;
pub mod migration;
pub mod negotiation;
pub mod semantic;
use crate::error::CoreError;
use std::collections::{BTreeSet, HashMap};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
pub use compatibility::{CompatibilityChecker, CompatibilityLevel, CompatibilityReport};
pub use deprecation::{DeprecationManager, DeprecationPolicy, DeprecationStatus};
pub use migration::{MigrationManager, MigrationPlan, MigrationStep};
pub use negotiation::{ClientCapabilities, NegotiationResult, VersionNegotiator};
pub use semantic::{Version, VersionBuilder, VersionConstraint, VersionRange};
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApiVersion {
pub version: Version,
pub release_date: chrono::DateTime<chrono::Utc>,
pub stability: StabilityLevel,
pub support_status: SupportStatus,
pub end_of_life: Option<chrono::DateTime<chrono::Utc>>,
pub features: BTreeSet<String>,
pub breakingchanges: Vec<String>,
pub new_features: Vec<String>,
pub bug_fixes: Vec<String>,
pub deprecated_features: Vec<String>,
pub min_clientversion: Option<Version>,
pub max_clientversion: Option<Version>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum StabilityLevel {
Experimental,
Alpha,
Beta,
Stable,
Mature,
Legacy,
}
impl StabilityLevel {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Experimental => "experimental",
Self::Alpha => "alpha",
Self::Beta => "beta",
Self::Stable => "stable",
Self::Mature => "mature",
Self::Legacy => "legacy",
}
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SupportStatus {
Active,
Maintenance,
Deprecated,
EndOfLife,
SecurityOnly,
}
impl SupportStatus {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Active => "active",
Self::Maintenance => "maintenance",
Self::Deprecated => "deprecated",
Self::EndOfLife => "end_of_life",
Self::SecurityOnly => "security_only",
}
}
}
pub struct VersionManager {
versions: HashMap<Version, ApiVersion>,
currentversion: Option<Version>,
compatibility_checker: CompatibilityChecker,
negotiator: VersionNegotiator,
migration_manager: MigrationManager,
deprecation_manager: DeprecationManager,
}
impl VersionManager {
#[must_use]
pub fn new() -> Self {
Self {
versions: HashMap::new(),
currentversion: None,
compatibility_checker: CompatibilityChecker::new(),
negotiator: VersionNegotiator::new(),
migration_manager: MigrationManager::new(),
deprecation_manager: DeprecationManager::new(),
}
}
pub fn registerversion(&mut self, apiversion: ApiVersion) -> Result<(), CoreError> {
let version = apiversion.version.clone();
if self.versions.contains_key(&version) {
return Err(CoreError::ComputationError(
crate::error::ErrorContext::new(format!("Version {version} is already registered")),
));
}
self.compatibility_checker.register_version(&apiversion)?;
self.migration_manager.register_version(&apiversion)?;
self.deprecation_manager.register_version(&apiversion)?;
self.versions.insert(version, apiversion);
Ok(())
}
pub fn set_currentversion(&mut self, version: Version) -> Result<(), CoreError> {
if !self.versions.contains_key(&version) {
return Err(CoreError::ComputationError(
crate::error::ErrorContext::new(format!("Version {version} is not registered")),
));
}
self.currentversion = Some(version);
Ok(())
}
#[must_use]
pub fn currentversion(&self) -> Option<&Version> {
self.currentversion.as_ref()
}
#[must_use]
pub fn getversions(&self) -> Vec<&ApiVersion> {
let mut versions: Vec<_> = self.versions.values().collect();
versions.sort_by(|a, b| a.version.cmp(&b.version));
versions
}
#[must_use]
pub fn get_supportedversions(&self) -> Vec<&ApiVersion> {
self.versions
.values()
.filter(|v| {
matches!(
v.support_status,
SupportStatus::Active | SupportStatus::Maintenance
)
})
.collect()
}
#[must_use]
pub fn getversion(&self, version: &Version) -> Option<&ApiVersion> {
self.versions.get(version)
}
pub fn check_compatibility(
&self,
fromversion: &Version,
toversion: &Version,
) -> Result<CompatibilityLevel, CoreError> {
self.compatibility_checker
.check_compatibility(fromversion, toversion)
}
pub fn get_compatibility_report(
&self,
fromversion: &Version,
toversion: &Version,
) -> Result<CompatibilityReport, CoreError> {
self.compatibility_checker
.get_compatibility_report(fromversion, toversion)
}
pub fn negotiateversion(
&self,
client_capabilities: &ClientCapabilities,
) -> Result<NegotiationResult, CoreError> {
let supportedversions: Vec<_> = self
.get_supportedversions()
.into_iter()
.map(|v| &v.version)
.collect();
self.negotiator
.negotiate(client_capabilities, &supportedversions)
}
pub fn get_migration_plan(
&self,
fromversion: &Version,
toversion: &Version,
) -> Result<MigrationPlan, CoreError> {
self.migration_manager
.create_migration_plan(fromversion, toversion)
}
#[must_use]
pub fn isversion_deprecated(&self, version: &Version) -> bool {
if let Some(apiversion) = self.versions.get(version) {
matches!(
apiversion.support_status,
SupportStatus::Deprecated | SupportStatus::EndOfLife
)
} else {
false
}
}
#[must_use]
pub fn get_deprecation_status(&self, version: &Version) -> Option<DeprecationStatus> {
self.deprecation_manager.get_deprecation_status(version)
}
pub fn update_deprecation_status(
&mut self,
version: &Version,
status: DeprecationStatus,
) -> Result<(), CoreError> {
self.deprecation_manager.update_status(version, status)
}
#[must_use]
pub fn get_latest_in_major(&self, major: u64) -> Option<&ApiVersion> {
self.versions
.values()
.filter(|v| v.version.major() == major)
.max_by(|a, b| a.version.cmp(&b.version))
}
#[must_use]
pub fn get_latest_stable(&self) -> Option<&ApiVersion> {
self.versions
.values()
.filter(|v| {
v.stability == StabilityLevel::Stable || v.stability == StabilityLevel::Mature
})
.filter(|v| v.support_status == SupportStatus::Active)
.max_by(|a, b| a.version.cmp(&b.version))
}
#[must_use]
pub fn has_upgrade_path(&self, fromversion: &Version, toversion: &Version) -> bool {
self.migration_manager
.has_migration_path(fromversion, toversion)
}
pub fn validate_constraint(
&self,
constraint: &VersionConstraint,
) -> Result<Vec<&Version>, CoreError> {
let matchingversions: Vec<_> = self
.versions
.keys()
.filter(|v| constraint.matches(v))
.collect();
Ok(matchingversions)
}
#[must_use]
pub fn getversion_statistics(&self) -> VersionStatistics {
let mut stats = VersionStatistics::default();
for apiversion in self.versions.values() {
stats.totalversions += 1;
match apiversion.stability {
StabilityLevel::Experimental => stats.experimentalversions += 1,
StabilityLevel::Alpha => stats.alphaversions += 1,
StabilityLevel::Beta => stats.betaversions += 1,
StabilityLevel::Stable => stats.stableversions += 1,
StabilityLevel::Mature => stats.matureversions += 1,
StabilityLevel::Legacy => stats.legacyversions += 1,
}
match apiversion.support_status {
SupportStatus::Active => stats.activeversions += 1,
SupportStatus::Maintenance => stats.maintenanceversions += 1,
SupportStatus::Deprecated => stats.deprecatedversions += 1,
SupportStatus::EndOfLife => stats.end_of_lifeversions += 1,
SupportStatus::SecurityOnly => stats.security_onlyversions += 1,
}
}
stats
}
pub fn perform_maintenance(&mut self) -> Result<MaintenanceReport, CoreError> {
let mut report = MaintenanceReport::default();
let now = chrono::Utc::now();
for (version, apiversion) in &mut self.versions {
if let Some(eol_date) = apiversion.end_of_life {
if now > eol_date && apiversion.support_status != SupportStatus::EndOfLife {
apiversion.support_status = SupportStatus::EndOfLife;
report.versions_marked_eol.push(version.clone());
}
}
}
let deprecation_updates = self.deprecation_manager.perform_maintenance()?;
report.deprecation_updates = deprecation_updates.len();
let migration_cleanup = self.migration_manager.cleanup_old_plans()?;
report.migration_plans_cleaned = migration_cleanup;
Ok(report)
}
}
impl Default for VersionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct VersionStatistics {
pub totalversions: usize,
pub experimentalversions: usize,
pub alphaversions: usize,
pub betaversions: usize,
pub stableversions: usize,
pub matureversions: usize,
pub legacyversions: usize,
pub activeversions: usize,
pub maintenanceversions: usize,
pub deprecatedversions: usize,
pub end_of_lifeversions: usize,
pub security_onlyversions: usize,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct MaintenanceReport {
pub versions_marked_eol: Vec<Version>,
pub deprecation_updates: usize,
pub migration_plans_cleaned: usize,
}
pub struct ApiVersionBuilder {
version: Option<Version>,
release_date: chrono::DateTime<chrono::Utc>,
stability: StabilityLevel,
support_status: SupportStatus,
end_of_life: Option<chrono::DateTime<chrono::Utc>>,
features: BTreeSet<String>,
breakingchanges: Vec<String>,
new_features: Vec<String>,
bug_fixes: Vec<String>,
deprecated_features: Vec<String>,
min_clientversion: Option<Version>,
max_clientversion: Option<Version>,
}
impl ApiVersionBuilder {
#[must_use]
pub fn new(version: Version) -> Self {
Self {
version: Some(version),
release_date: chrono::Utc::now(),
stability: StabilityLevel::Stable,
support_status: SupportStatus::Active,
end_of_life: None,
features: BTreeSet::new(),
breakingchanges: Vec::new(),
new_features: Vec::new(),
bug_fixes: Vec::new(),
deprecated_features: Vec::new(),
min_clientversion: None,
max_clientversion: None,
}
}
#[must_use]
pub fn release_date(mut self, date: chrono::DateTime<chrono::Utc>) -> Self {
self.release_date = date;
self
}
#[must_use]
pub fn stability(mut self, stability: StabilityLevel) -> Self {
self.stability = stability;
self
}
#[must_use]
pub fn support_status(mut self, status: SupportStatus) -> Self {
self.support_status = status;
self
}
#[must_use]
pub fn end_of_life(mut self, date: chrono::DateTime<chrono::Utc>) -> Self {
self.end_of_life = Some(date);
self
}
#[must_use]
pub fn feature(mut self, feature: &str) -> Self {
self.features.insert(feature.to_string());
self
}
#[must_use]
pub fn breaking_change(mut self, change: &str) -> Self {
self.breakingchanges.push(change.to_string());
self
}
#[must_use]
pub fn new_feature(mut self, feature: &str) -> Self {
self.new_features.push(feature.to_string());
self
}
#[must_use]
pub fn bug_fix(mut self, fix: &str) -> Self {
self.bug_fixes.push(fix.to_string());
self
}
#[must_use]
pub fn deprecated_feature(mut self, feature: &str) -> Self {
self.deprecated_features.push(feature.to_string());
self
}
#[must_use]
pub fn min_clientversion(mut self, version: Version) -> Self {
self.min_clientversion = Some(version);
self
}
#[must_use]
pub fn max_clientversion(mut self, version: Version) -> Self {
self.max_clientversion = Some(version);
self
}
pub fn build(self) -> Result<ApiVersion, CoreError> {
let version = self.version.ok_or_else(|| {
CoreError::ComputationError(crate::error::ErrorContext::new(
"Version is required".to_string(),
))
})?;
Ok(ApiVersion {
version,
release_date: self.release_date,
stability: self.stability,
support_status: self.support_status,
end_of_life: self.end_of_life,
features: self.features,
breakingchanges: self.breakingchanges,
new_features: self.new_features,
bug_fixes: self.bug_fixes,
deprecated_features: self.deprecated_features,
min_clientversion: self.min_clientversion,
max_clientversion: self.max_clientversion,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn testversion_manager_creation() {
let manager = VersionManager::new();
assert!(manager.currentversion().is_none());
assert_eq!(manager.getversions().len(), 0);
}
#[test]
fn test_apiversion_builder() {
let version = Version::parse("1.0.0").expect("Operation failed");
let apiversion = ApiVersionBuilder::new(version)
.stability(StabilityLevel::Stable)
.feature("feature1")
.new_feature("New awesome feature")
.build()
.expect("Operation failed");
assert_eq!(apiversion.version.to_string(), "1.0.0");
assert_eq!(apiversion.stability, StabilityLevel::Stable);
assert!(apiversion.features.contains("feature1"));
assert_eq!(apiversion.new_features.len(), 1);
}
#[test]
fn testversion_registration() {
let mut manager = VersionManager::new();
let version = Version::parse("1.0.0").expect("Operation failed");
let apiversion = ApiVersionBuilder::new(version.clone())
.build()
.expect("Operation failed");
manager
.registerversion(apiversion)
.expect("Operation failed");
assert_eq!(manager.getversions().len(), 1);
assert!(manager.getversion(&version).is_some());
}
#[test]
fn test_currentversion_setting() {
let mut manager = VersionManager::new();
let version = Version::parse("1.0.0").expect("Operation failed");
let apiversion = ApiVersionBuilder::new(version.clone())
.build()
.expect("Operation failed");
manager
.registerversion(apiversion)
.expect("Operation failed");
manager
.set_currentversion(version.clone())
.expect("Operation failed");
assert_eq!(manager.currentversion(), Some(&version));
}
#[test]
fn test_stability_levels() {
assert_eq!(StabilityLevel::Experimental.as_str(), "experimental");
assert_eq!(StabilityLevel::Stable.as_str(), "stable");
assert_eq!(StabilityLevel::Mature.as_str(), "mature");
assert!(StabilityLevel::Experimental < StabilityLevel::Alpha);
assert!(StabilityLevel::Stable > StabilityLevel::Beta);
}
#[test]
fn test_support_status() {
assert_eq!(SupportStatus::Active.as_str(), "active");
assert_eq!(SupportStatus::Deprecated.as_str(), "deprecated");
assert_eq!(SupportStatus::EndOfLife.as_str(), "end_of_life");
}
#[test]
fn testversion_statistics() {
let mut manager = VersionManager::new();
let v1 = ApiVersionBuilder::new(Version::parse("1.0.0").expect("Operation failed"))
.stability(StabilityLevel::Stable)
.build()
.expect("Operation failed");
let v2 = ApiVersionBuilder::new(Version::parse("2.0.0").expect("Operation failed"))
.stability(StabilityLevel::Beta)
.support_status(SupportStatus::Maintenance)
.build()
.expect("Operation failed");
manager.registerversion(v1).expect("Operation failed");
manager.registerversion(v2).expect("Operation failed");
let stats = manager.getversion_statistics();
assert_eq!(stats.totalversions, 2);
assert_eq!(stats.stableversions, 1);
assert_eq!(stats.betaversions, 1);
assert_eq!(stats.activeversions, 1);
assert_eq!(stats.maintenanceversions, 1);
}
}