nu_protocol/config/
plugin_gc.rs

1use super::prelude::*;
2use crate as nu_protocol;
3use std::collections::HashMap;
4
5/// Configures when plugins should be stopped if inactive
6#[derive(Clone, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
7pub struct PluginGcConfigs {
8    /// The config to use for plugins not otherwise specified
9    pub default: PluginGcConfig,
10    /// Specific configs for plugins (by name)
11    pub plugins: HashMap<String, PluginGcConfig>,
12}
13
14impl PluginGcConfigs {
15    /// Get the plugin GC configuration for a specific plugin name. If not specified by name in the
16    /// config, this is `default`.
17    pub fn get(&self, plugin_name: &str) -> &PluginGcConfig {
18        self.plugins.get(plugin_name).unwrap_or(&self.default)
19    }
20}
21
22impl UpdateFromValue for PluginGcConfigs {
23    fn update<'a>(
24        &mut self,
25        value: &'a Value,
26        path: &mut ConfigPath<'a>,
27        errors: &mut ConfigErrors,
28    ) {
29        let Value::Record { val: record, .. } = value else {
30            errors.type_mismatch(path, Type::record(), value);
31            return;
32        };
33
34        for (col, val) in record.iter() {
35            let path = &mut path.push(col);
36            match col.as_str() {
37                "default" => self.default.update(val, path, errors),
38                "plugins" => self.plugins.update(val, path, errors),
39                _ => errors.unknown_option(path, val),
40            }
41        }
42    }
43}
44
45/// Configures when a plugin should be stopped if inactive
46#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub struct PluginGcConfig {
48    /// True if the plugin should be stopped automatically
49    pub enabled: bool,
50    /// When to stop the plugin if not in use for this long (in nanoseconds)
51    pub stop_after: i64,
52}
53
54impl Default for PluginGcConfig {
55    fn default() -> Self {
56        PluginGcConfig {
57            enabled: true,
58            stop_after: 10_000_000_000, // 10sec
59        }
60    }
61}
62
63impl IntoValue for PluginGcConfig {
64    fn into_value(self, span: Span) -> Value {
65        record! {
66            "enabled" => self.enabled.into_value(span),
67            "stop_after" => Value::duration(self.stop_after, span),
68        }
69        .into_value(span)
70    }
71}
72
73impl UpdateFromValue for PluginGcConfig {
74    fn update<'a>(
75        &mut self,
76        value: &'a Value,
77        path: &mut ConfigPath<'a>,
78        errors: &mut ConfigErrors,
79    ) {
80        let Value::Record { val: record, .. } = value else {
81            errors.type_mismatch(path, Type::record(), value);
82            return;
83        };
84
85        for (col, val) in record.iter() {
86            let path = &mut path.push(col);
87            match col.as_str() {
88                "enabled" => self.enabled.update(val, path, errors),
89                "stop_after" => {
90                    if let Ok(duration) = val.as_duration() {
91                        if duration >= 0 {
92                            self.stop_after = duration;
93                        } else {
94                            errors.invalid_value(path, "a non-negative duration", val);
95                        }
96                    } else {
97                        errors.type_mismatch(path, Type::Duration, val);
98                    }
99                }
100                _ => errors.unknown_option(path, val),
101            }
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::{Config, Span, record};
110
111    fn test_pair() -> (PluginGcConfigs, Value) {
112        (
113            PluginGcConfigs {
114                default: PluginGcConfig {
115                    enabled: true,
116                    stop_after: 30_000_000_000,
117                },
118                plugins: [(
119                    "my_plugin".to_owned(),
120                    PluginGcConfig {
121                        enabled: false,
122                        stop_after: 0,
123                    },
124                )]
125                .into_iter()
126                .collect(),
127            },
128            Value::test_record(record! {
129                "default" => Value::test_record(record! {
130                    "enabled" => Value::test_bool(true),
131                    "stop_after" => Value::test_duration(30_000_000_000),
132                }),
133                "plugins" => Value::test_record(record! {
134                    "my_plugin" => Value::test_record(record! {
135                        "enabled" => Value::test_bool(false),
136                        "stop_after" => Value::test_duration(0),
137                    }),
138                }),
139            }),
140        )
141    }
142
143    #[test]
144    fn update() {
145        let (expected, input) = test_pair();
146        let config = Config::default();
147        let mut errors = ConfigErrors::new(&config);
148        let mut result = PluginGcConfigs::default();
149        result.update(&input, &mut ConfigPath::new(), &mut errors);
150        assert!(errors.is_empty(), "errors: {errors:#?}");
151        assert_eq!(expected, result);
152    }
153
154    #[test]
155    fn reconstruct() {
156        let (input, expected) = test_pair();
157        assert_eq!(expected, input.into_value(Span::test_data()));
158    }
159}