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