use super::DynamicConfig;
use serde::{ Deserialize, Serialize };
use std::collections::HashMap;
use std::time::SystemTime;
#[ derive( Debug, Clone, PartialEq, Eq ) ]
pub enum ConfigChangeType
{
Update,
Rollback,
FileLoad,
VersionRestore,
}
#[ derive( Debug, Clone ) ]
pub struct ConfigChangeEvent
{
pub version_id : String,
pub change_type : ConfigChangeType,
pub timestamp : SystemTime,
pub previous_config : Option< DynamicConfig >,
pub new_config : DynamicConfig,
}
#[ derive( Debug, Clone ) ]
pub struct ConfigHistoryEntry
{
pub version_id : String,
pub config : DynamicConfig,
pub timestamp : SystemTime,
pub change_type : ConfigChangeType,
pub config_hash : u64,
pub size_bytes : usize,
pub delta : Option< ConfigDelta >,
pub created_by : Option< String >,
pub description : Option< String >,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct ConfigDelta
{
pub changed_fields : HashMap< String, serde_json::Value >,
pub added_fields : HashMap< String, serde_json::Value >,
pub removed_fields : Vec< String >,
pub tag_changes : HashMap< String, Option< String > >, }
impl ConfigDelta
{
pub fn create_delta( old_config : &DynamicConfig, new_config : &DynamicConfig ) -> Self
{
let mut changed_fields = HashMap::new();
let added_fields = HashMap::new();
let removed_fields = Vec::new();
let mut tag_changes = HashMap::new();
if old_config.timeout != new_config.timeout
{
changed_fields.insert( "timeout".to_string(), serde_json::to_value( &new_config.timeout ).unwrap() );
}
if old_config.retry_attempts != new_config.retry_attempts
{
changed_fields.insert( "retry_attempts".to_string(), serde_json::to_value( &new_config.retry_attempts ).unwrap() );
}
if old_config.base_url != new_config.base_url
{
changed_fields.insert( "base_url".to_string(), serde_json::to_value( &new_config.base_url ).unwrap() );
}
if old_config.enable_jitter != new_config.enable_jitter
{
changed_fields.insert( "enable_jitter".to_string(), serde_json::to_value( &new_config.enable_jitter ).unwrap() );
}
if old_config.max_retry_delay != new_config.max_retry_delay
{
changed_fields.insert( "max_retry_delay".to_string(), serde_json::to_value( &new_config.max_retry_delay ).unwrap() );
}
if old_config.base_retry_delay != new_config.base_retry_delay
{
changed_fields.insert( "base_retry_delay".to_string(), serde_json::to_value( &new_config.base_retry_delay ).unwrap() );
}
if old_config.backoff_multiplier != new_config.backoff_multiplier
{
changed_fields.insert( "backoff_multiplier".to_string(), serde_json::to_value( &new_config.backoff_multiplier ).unwrap() );
}
if old_config.source_priority != new_config.source_priority
{
changed_fields.insert( "source_priority".to_string(), serde_json::to_value( &new_config.source_priority ).unwrap() );
}
for ( key, old_value ) in &old_config.tags
{
match new_config.tags.get( key )
{
Some( new_value ) => {
if old_value != new_value
{
tag_changes.insert( key.clone(), Some( new_value.clone() ) );
}
},
None => {
tag_changes.insert( key.clone(), None ); }
}
}
for ( key, new_value ) in &new_config.tags
{
if !old_config.tags.contains_key( key )
{
tag_changes.insert( key.clone(), Some( new_value.clone() ) );
}
}
Self {
changed_fields,
added_fields,
removed_fields,
tag_changes,
}
}
pub fn apply_delta( &self, base_config : &DynamicConfig ) -> Result< DynamicConfig, serde_json::Error >
{
let mut new_config = base_config.clone();
for ( field, value ) in &self.changed_fields
{
match field.as_str()
{
"timeout" => new_config.timeout = serde_json::from_value( value.clone() )?,
"retry_attempts" => new_config.retry_attempts = serde_json::from_value( value.clone() )?,
"base_url" => new_config.base_url = serde_json::from_value( value.clone() )?,
"enable_jitter" => new_config.enable_jitter = serde_json::from_value( value.clone() )?,
"max_retry_delay" => new_config.max_retry_delay = serde_json::from_value( value.clone() )?,
"base_retry_delay" => new_config.base_retry_delay = serde_json::from_value( value.clone() )?,
"backoff_multiplier" => new_config.backoff_multiplier = serde_json::from_value( value.clone() )?,
"source_priority" => new_config.source_priority = serde_json::from_value( value.clone() )?,
_ => {} }
}
for ( key, change ) in &self.tag_changes
{
match change
{
Some( new_value ) => {
new_config.tags.insert( key.clone(), new_value.clone() );
},
None => {
new_config.tags.remove( key );
}
}
}
new_config.validation_hash = None;
Ok( new_config )
}
pub fn memory_footprint( &self ) -> usize
{
serde_json ::to_string( self ).unwrap_or_default().len()
}
}
impl ConfigHistoryEntry
{
pub fn from_config( config : DynamicConfig, change_type : ConfigChangeType, version_id : String ) -> Self
{
let config_hash = config.compute_hash();
let size_bytes = serde_json::to_string( &config ).unwrap_or_default().len();
Self {
version_id,
config,
timestamp : SystemTime::now(),
change_type,
config_hash,
size_bytes,
delta : None,
created_by : None,
description : None,
}
}
pub fn from_config_with_delta(
config : DynamicConfig,
change_type : ConfigChangeType,
version_id : String,
previous_config : Option< &DynamicConfig >,
created_by : Option< String >,
description : Option< String >
) -> Self
{
let config_hash = config.compute_hash();
let size_bytes = serde_json::to_string( &config ).unwrap_or_default().len();
let delta = if let Some( prev ) = previous_config
{
Some( ConfigDelta::create_delta( prev, &config ) )
} else {
None
};
Self {
version_id,
config,
timestamp : SystemTime::now(),
change_type,
config_hash,
size_bytes,
delta,
created_by,
description,
}
}
pub fn effective_memory_footprint( &self ) -> usize
{
if let Some( delta ) = &self.delta
{
delta.memory_footprint()
} else {
self.size_bytes
}
}
pub fn can_compress( &self ) -> bool
{
self.delta.is_some()
}
pub fn reconstruct_config( &self, base_config : &DynamicConfig ) -> Result< DynamicConfig, serde_json::Error >
{
if let Some( delta ) = &self.delta
{
delta.apply_delta( base_config )
} else {
Ok( self.config.clone() )
}
}
}