use super::prelude::*; use crate as nu_protocol; use std::collections::HashMap; /// Configures when plugins should be stopped if inactive #[derive(Clone, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)] pub struct PluginGcConfigs { /// The config to use for plugins not otherwise specified pub default: PluginGcConfig, /// Specific configs for plugins (by name) pub plugins: HashMap, } impl PluginGcConfigs { /// Get the plugin GC configuration for a specific plugin name. If not specified by name in the /// config, this is `default`. pub fn get(&self, plugin_name: &str) -> &PluginGcConfig { self.plugins.get(plugin_name).unwrap_or(&self.default) } } impl UpdateFromValue for PluginGcConfigs { fn update<'a>( &mut self, value: &'a Value, path: &mut ConfigPath<'a>, errors: &mut ConfigErrors, ) { let Value::Record { val: record, .. } = value else { errors.type_mismatch(path, Type::record(), value); return; }; for (col, val) in record.iter() { let path = &mut path.push(col); match col.as_str() { "default" => self.default.update(val, path, errors), "plugins" => self.plugins.update(val, path, errors), _ => errors.unknown_option(path, val), } } } } /// Configures when a plugin should be stopped if inactive #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PluginGcConfig { /// True if the plugin should be stopped automatically pub enabled: bool, /// When to stop the plugin if not in use for this long (in nanoseconds) pub stop_after: i64, } impl Default for PluginGcConfig { fn default() -> Self { PluginGcConfig { enabled: true, stop_after: 10_000_000_000, // 10sec } } } impl IntoValue for PluginGcConfig { fn into_value(self, span: Span) -> Value { record! { "enabled" => self.enabled.into_value(span), "stop_after" => Value::duration(self.stop_after, span), } .into_value(span) } } impl UpdateFromValue for PluginGcConfig { fn update<'a>( &mut self, value: &'a Value, path: &mut ConfigPath<'a>, errors: &mut ConfigErrors, ) { let Value::Record { val: record, .. } = value else { errors.type_mismatch(path, Type::record(), value); return; }; for (col, val) in record.iter() { let path = &mut path.push(col); match col.as_str() { "enabled" => self.enabled.update(val, path, errors), "stop_after" => { if let Ok(duration) = val.as_duration() { if duration >= 0 { self.stop_after = duration; } else { errors.invalid_value(path, "a non-negative duration", val); } } else { errors.type_mismatch(path, Type::Duration, val); } } _ => errors.unknown_option(path, val), } } } } #[cfg(test)] mod tests { use super::*; use crate::{record, Config, Span}; fn test_pair() -> (PluginGcConfigs, Value) { ( PluginGcConfigs { default: PluginGcConfig { enabled: true, stop_after: 30_000_000_000, }, plugins: [( "my_plugin".to_owned(), PluginGcConfig { enabled: false, stop_after: 0, }, )] .into_iter() .collect(), }, Value::test_record(record! { "default" => Value::test_record(record! { "enabled" => Value::test_bool(true), "stop_after" => Value::test_duration(30_000_000_000), }), "plugins" => Value::test_record(record! { "my_plugin" => Value::test_record(record! { "enabled" => Value::test_bool(false), "stop_after" => Value::test_duration(0), }), }), }), ) } #[test] fn update() { let (expected, input) = test_pair(); let config = Config::default(); let mut errors = ConfigErrors::new(&config); let mut result = PluginGcConfigs::default(); result.update(&input, &mut ConfigPath::new(), &mut errors); assert!(errors.is_empty(), "errors: {errors:#?}"); assert_eq!(expected, result); } #[test] fn reconstruct() { let (input, expected) = test_pair(); assert_eq!(expected, input.into_value(Span::test_data())); } }