#[ cfg( feature = "dynamic_config" ) ]
mod private
{
use core::time::Duration;
use serde::{ Serialize, Deserialize };
use error_tools::untyped::{ format_err, Result as OllamaResult };
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct DynamicConfig
{
pub server_url : String,
pub timeout : Duration,
pub max_connections : usize,
pub enable_caching : bool,
}
impl DynamicConfig
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
server_url : "http://localhost:11434".to_string(),
timeout : Duration::from_secs( 30 ),
max_connections : 5,
enable_caching : false,
}
}
#[ inline ]
pub fn validate( &self ) -> OllamaResult< () >
{
if self.server_url.is_empty()
{
return Err( format_err!( "Server URL cannot be empty" ) );
}
if self.timeout.is_zero()
{
return Err( format_err!( "Timeout cannot be zero" ) );
}
if self.max_connections == 0
{
return Err( format_err!( "Max connections must be greater than zero" ) );
}
Ok( () )
}
#[ inline ]
pub fn from_env() -> OllamaResult< Self >
{
let mut config = Self::new();
if let Ok( url ) = std::env::var( "OLLAMA_SERVER_URL" )
{
config.server_url = url;
}
if let Ok( timeout_str ) = std::env::var( "OLLAMA_TIMEOUT_SECS" )
{
let timeout_secs : u64 = timeout_str.parse()
.map_err( | e | format_err!( "Invalid timeout value : {}", e ) )?;
config.timeout = Duration::from_secs( timeout_secs );
}
if let Ok( connections_str ) = std::env::var( "OLLAMA_MAX_CONNECTIONS" )
{
config.max_connections = connections_str.parse()
.map_err( | e | format_err!( "Invalid max connections value : {}", e ) )?;
}
if let Ok( caching_str ) = std::env::var( "OLLAMA_ENABLE_CACHING" )
{
config.enable_caching = caching_str.parse()
.map_err( | e | format_err!( "Invalid caching flag : {}", e ) )?;
}
config.validate()?;
Ok( config )
}
#[ inline ]
pub fn from_file< P: AsRef< std::path::Path > >( path : P ) -> OllamaResult< Self >
{
let content = std::fs::read_to_string( path )
.map_err( | e | format_err!( "Failed to read config file : {}", e ) )?;
let config : Self = serde_json::from_str( &content )
.map_err( | e | format_err!( "Failed to parse config file : {}", e ) )?;
config.validate()?;
Ok( config )
}
#[ inline ]
pub fn save_to_file< P: AsRef< std::path::Path > >( &self, path : P ) -> OllamaResult< () >
{
let content = serde_json::to_string_pretty( self )
.map_err( | e | format_err!( "Failed to serialize config : {}", e ) )?;
std ::fs::write( path, content )
.map_err( | e | format_err!( "Failed to write config file : {}", e ) )?;
Ok( () )
}
}
impl Default for DynamicConfig
{
fn default() -> Self
{
Self::new()
}
}
#[ derive( Debug, Clone ) ]
pub struct ConfigDiff
{
pub server_url_changed : bool,
pub timeout_changed : bool,
pub max_connections_changed : bool,
pub caching_changed : bool,
}
impl ConfigDiff
{
#[ inline ]
#[ must_use ]
pub fn calculate( old : &DynamicConfig, new : &DynamicConfig ) -> Self
{
Self
{
server_url_changed : old.server_url != new.server_url,
timeout_changed : old.timeout != new.timeout,
max_connections_changed : old.max_connections != new.max_connections,
caching_changed : old.enable_caching != new.enable_caching,
}
}
#[ inline ]
pub fn has_changes( &self ) -> bool
{
self.server_url_changed || self.timeout_changed ||
self.max_connections_changed || self.caching_changed
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct ConfigBackup
{
pub config : DynamicConfig,
pub timestamp : std::time::SystemTime,
pub version : u64,
}
impl ConfigBackup
{
#[ inline ]
#[ must_use ]
pub fn from_config( config : &DynamicConfig ) -> Self
{
Self
{
config : config.clone(),
timestamp : std::time::SystemTime::now(),
version : 0, }
}
#[ inline ]
pub fn to_config( &self ) -> DynamicConfig
{
self.config.clone()
}
}
#[ derive( Debug, Clone ) ]
pub struct ConfigVersion
{
pub config : DynamicConfig,
pub version : u64,
pub created_at : std::time::SystemTime,
}
impl ConfigVersion
{
#[ inline ]
#[ must_use ]
pub fn new( config : DynamicConfig, version : u64 ) -> Self
{
Self
{
config,
version,
created_at : std::time::SystemTime::now(),
}
}
#[ inline ]
pub fn config( &self ) -> &DynamicConfig
{
&self.config
}
#[ inline ]
pub fn version( &self ) -> u64
{
self.version
}
}
#[ derive( Debug ) ]
pub struct DynamicConfigManager
{
current : DynamicConfig,
history : Vec< ConfigVersion >,
max_history : usize,
}
impl DynamicConfigManager
{
#[ inline ]
pub fn new() -> OllamaResult< Self >
{
let config = DynamicConfig::new();
config.validate()?;
let version = ConfigVersion::new( config.clone(), 0 );
Ok( Self {
current : config,
history : vec![ version ],
max_history : 10,
})
}
#[ inline ]
#[ must_use ]
pub fn from_config( config : DynamicConfig ) -> Self
{
let version = ConfigVersion::new( config.clone(), 0 );
Self
{
current : config,
history : vec![ version ],
max_history : 10,
}
}
#[ inline ]
pub fn from_file< P: AsRef< std::path::Path > >( path : P ) -> OllamaResult< Self >
{
let config = DynamicConfig::from_file( path )?;
Ok( Self::from_config( config ) )
}
#[ inline ]
pub fn current( &self ) -> &DynamicConfig
{
&self.current
}
#[ inline ]
pub fn get_current_config( &self ) -> &DynamicConfig
{
self.current()
}
#[ inline ]
pub fn update( &mut self, new_config : DynamicConfig ) -> OllamaResult< () >
{
new_config.validate()?;
let diff = ConfigDiff::calculate( &self.current, &new_config );
if !diff.has_changes()
{
return Ok( () ); }
let new_version = self.history.len() as u64;
let version = ConfigVersion::new( new_config.clone(), new_version );
self.current = new_config;
self.history.push( version );
if self.history.len() > self.max_history
{
self.history.remove( 0 );
}
Ok( () )
}
#[ inline ]
pub fn update_config( &mut self, new_config : DynamicConfig ) -> OllamaResult< () >
{
self.update( new_config )
}
#[ inline ]
pub fn history( &self ) -> &[ ConfigVersion ]
{
&self.history
}
#[ inline ]
pub fn rollback( &mut self, version : u64 ) -> OllamaResult< () >
{
let config_version = self.history.iter()
.find( | v | v.version == version )
.ok_or_else( || format_err!( "Version {} not found in history", version ) )?;
self.current = config_version.config.clone();
Ok( () )
}
#[ inline ]
pub fn rollback_to_version( &mut self, version : u64 ) -> OllamaResult< () >
{
self.rollback( version )
}
#[ inline ]
pub fn get_config_history( &self ) -> &[ ConfigVersion ]
{
self.history()
}
#[ inline ]
pub fn apply_env_overrides( &mut self ) -> OllamaResult< () >
{
let env_config = DynamicConfig::from_env()?;
self.update( env_config )
}
#[ inline ]
pub fn on_config_change< F >( &mut self, _callback : F ) -> OllamaResult< () >
where
F : Fn( &DynamicConfig ) + 'static,
{
Ok( () )
}
#[ inline ]
#[ must_use ]
pub fn diff_from_version( &self, version : u64 ) -> Option< ConfigDiff >
{
self.history.iter()
.find( | v | v.version == version )
.map( | v | ConfigDiff::calculate( &v.config, &self.current ) )
}
#[ inline ]
pub fn clear_history( &mut self )
{
self.history.clear();
self.history.push( ConfigVersion::new( self.current.clone(), 0 ) );
}
#[ inline ]
pub fn apply_to_client( &self, _client : &mut crate::client::OllamaClient ) -> OllamaResult< () >
{
Ok( () )
}
}
}
#[ cfg( feature = "dynamic_config" ) ]
crate ::mod_interface!
{
exposed use private::DynamicConfig;
exposed use private::ConfigDiff;
exposed use private::ConfigBackup;
exposed use private::ConfigVersion;
exposed use private::DynamicConfigManager;
}