Skip to main content

cordis_include/
options.rs

1//! Serializable entry description — the on-disk shape of one plugin entry.
2
3use crate::node::Node;
4use serde::{Deserialize, Serialize};
5
6/// Whether a boolean is `false` (used by `skip_serializing_if`).
7fn is_false(value: &bool) -> bool {
8    !*value
9}
10
11/// Entry name that mounts another config file as a subtree
12/// (`name: import` with `config: { url: "…" }`).
13pub const IMPORT_NAME: &str = "import";
14
15/// One entry in a config file: a plugin instance plus its group position.
16///
17/// The declared field order is the serialization order (`id` and `name`
18/// first, `config` last), keeping files readable and diff-stable. Entries
19/// with a `group` array are groups; the array order is the child order.
20///
21/// `config` is stored raw: `${{ env.NAME }}` templates stay intact in the
22/// entry tree and are only expanded when the config is handed to a plugin
23/// (see [`crate::Entry::resolved_config`]).
24#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
25pub struct EntryOptions {
26    /// Stable identity of the entry. Missing ids are filled with a random
27    /// 6-character base36 id when the entry enters a tree and persisted on
28    /// the next write-back.
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub id: Option<String>,
31    /// Plugin name used to resolve the plugin implementation.
32    #[serde(default)]
33    pub name: String,
34    /// Whether the entry (and transitively its subtree) is disabled.
35    #[serde(default, skip_serializing_if = "is_false")]
36    pub disabled: bool,
37    /// Names of services that must be active before this entry starts.
38    #[serde(default, skip_serializing_if = "Vec::is_empty")]
39    pub inject: Vec<String>,
40    /// Child entries, in order. Non-empty only for group entries.
41    #[serde(default, skip_serializing_if = "Vec::is_empty")]
42    pub group: Vec<EntryOptions>,
43    /// Raw plugin configuration (templates unexpanded).
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub config: Option<Node>,
46}
47
48impl EntryOptions {
49    /// Create options for a plugin with the given name.
50    pub fn new(name: impl Into<String>) -> Self {
51        Self {
52            name: name.into(),
53            ..Self::default()
54        }
55    }
56
57    /// Set the explicit entry id.
58    pub fn with_id(mut self, id: impl Into<String>) -> Self {
59        self.id = Some(id.into());
60        self
61    }
62
63    /// Set the raw plugin configuration.
64    pub fn with_config(mut self, config: Node) -> Self {
65        self.config = Some(config);
66        self
67    }
68
69    /// Set the child entries (turning this entry into a group).
70    pub fn with_group(mut self, group: Vec<EntryOptions>) -> Self {
71        self.group = group;
72        self
73    }
74
75    /// Mark the entry (and its subtree) as disabled.
76    pub fn with_disabled(mut self, disabled: bool) -> Self {
77        self.disabled = disabled;
78        self
79    }
80
81    /// The url this import entry mounts, when the entry is an import
82    /// (`name: import` with a string `config.url`).
83    pub fn import_url(&self) -> Option<&str> {
84        if self.name != IMPORT_NAME {
85            return None;
86        }
87        self.config.as_ref()?.as_object()?.get("url")?.as_str()
88    }
89
90    /// Declare services that must be active before this entry starts.
91    pub fn with_inject<I, S>(mut self, inject: I) -> Self
92    where
93        I: IntoIterator<Item = S>,
94        S: Into<String>,
95    {
96        self.inject = inject.into_iter().map(Into::into).collect();
97        self
98    }
99}