Skip to main content

fresh_core/
config.rs

1//! Configuration types shared across crates
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7fn default_true() -> bool {
8    true
9}
10
11fn settings_is_empty(v: &serde_json::Value) -> bool {
12    match v {
13        serde_json::Value::Null => true,
14        serde_json::Value::Object(o) => o.is_empty(),
15        _ => false,
16    }
17}
18
19/// Configuration for a single plugin
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
21#[schemars(extend("x-display-field" = "/enabled"))]
22pub struct PluginConfig {
23    /// Whether this plugin is enabled (default: true)
24    /// When disabled, the plugin will not be loaded or executed.
25    #[serde(default = "default_true")]
26    pub enabled: bool,
27
28    /// Path to the plugin file (populated automatically when scanning)
29    /// This is filled in by the plugin system and should not be set manually.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    #[schemars(extend("readOnly" = true))]
32    pub path: Option<PathBuf>,
33
34    /// Plugin-specific settings. The shape is defined by each plugin's
35    /// `<plugin_name>.schema.json` sidecar file; the host stores the value as
36    /// untyped JSON so a malformed plugin schema can't poison the rest of the
37    /// config. Plugins read this via `editor.getPluginConfig()` and the
38    /// Settings UI renders it as a sub-category under "Plugin Settings".
39    #[serde(default, skip_serializing_if = "settings_is_empty")]
40    #[schemars(extend("readOnly" = true))]
41    pub settings: serde_json::Value,
42}
43
44impl Default for PluginConfig {
45    fn default() -> Self {
46        Self {
47            enabled: true,
48            path: None,
49            settings: serde_json::Value::Null,
50        }
51    }
52}
53
54impl PluginConfig {
55    pub fn new_with_path(path: PathBuf) -> Self {
56        Self {
57            enabled: true,
58            path: Some(path),
59            settings: serde_json::Value::Null,
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    /// An empty config `{}` deserializes with `enabled = true` (from the
69    /// `default_true` helper) and no path. An explicit `false` is preserved.
70    #[test]
71    fn enabled_defaults_to_true_when_missing() {
72        let c: PluginConfig = serde_json::from_str("{}").unwrap();
73        assert!(c.enabled);
74        assert!(c.path.is_none());
75
76        let c: PluginConfig = serde_json::from_str(r#"{"enabled": false}"#).unwrap();
77        assert!(!c.enabled);
78    }
79
80    /// `new_with_path` populates the path field, unlike `Default::default()`
81    /// which leaves it `None`.
82    #[test]
83    fn new_with_path_sets_path_and_enabled() {
84        let p = PathBuf::from("/plugins/foo.js");
85        let c = PluginConfig::new_with_path(p.clone());
86        assert!(c.enabled);
87        assert_eq!(c.path.as_ref(), Some(&p));
88    }
89}