liminal_server/services/templates/
configuration.rs1use super::TemplatePolicy;
4use crate::config::types::ServiceTemplateConfig;
5use serde::{Deserialize, Deserializer};
6
7#[derive(Clone, Debug, PartialEq, Eq, Default)]
9pub struct TemplateSources {
10 configured: Vec<ServiceTemplateConfig>,
11 pub policy: Option<TemplatePolicy>,
13}
14impl TemplateSources {
15 #[must_use]
17 pub fn is_empty(&self) -> bool {
18 self.configured.is_empty() && self.policy.is_none()
19 }
20}
21
22impl<'de> Deserialize<'de> for TemplateSources {
23 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
24 #[derive(Deserialize)]
25 #[serde(deny_unknown_fields)]
26 struct Table {
27 #[serde(default)]
28 configured: Vec<ServiceTemplateConfig>,
29 roots: Vec<std::path::PathBuf>,
30 command_bytes: std::num::NonZeroU64,
31 records: std::num::NonZeroUsize,
32 arguments: std::num::NonZeroUsize,
33 environment_names: std::num::NonZeroUsize,
34 text_bytes: std::num::NonZeroUsize,
35 stop_timeout_ms: std::num::NonZeroU64,
36 }
37 #[derive(Deserialize)]
38 #[serde(untagged)]
39 enum Wire {
40 Legacy(Vec<ServiceTemplateConfig>),
41 Table(Table),
42 }
43 Ok(match Wire::deserialize(deserializer)? {
44 Wire::Legacy(configured) => Self {
45 configured,
46 policy: None,
47 },
48 Wire::Table(table) => Self {
49 configured: table.configured,
50 policy: Some(TemplatePolicy {
51 roots: table.roots,
52 command_bytes: table.command_bytes,
53 records: table.records,
54 arguments: table.arguments,
55 environment_names: table.environment_names,
56 text_bytes: table.text_bytes,
57 stop_timeout_ms: table.stop_timeout_ms,
58 }),
59 },
60 })
61 }
62}
63impl std::ops::Deref for TemplateSources {
64 type Target = [ServiceTemplateConfig];
65 fn deref(&self) -> &Self::Target {
66 &self.configured
67 }
68}
69impl<'a> IntoIterator for &'a TemplateSources {
70 type Item = &'a ServiceTemplateConfig;
71 type IntoIter = std::slice::Iter<'a, ServiceTemplateConfig>;
72 fn into_iter(self) -> Self::IntoIter {
73 self.configured.iter()
74 }
75}
76impl From<Vec<ServiceTemplateConfig>> for TemplateSources {
77 fn from(configured: Vec<ServiceTemplateConfig>) -> Self {
78 Self {
79 configured,
80 policy: None,
81 }
82 }
83}
84
85pub fn collect_errors(config: &crate::config::types::ServerConfig, errors: &mut Vec<String>) {
87 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
88 return;
89 };
90 if let Some(policy) = &config.template_sources().policy {
91 let place = config.templates_place();
92 if policy.roots.is_empty()
93 || policy
94 .roots
95 .iter()
96 .any(|root| !root.is_absolute() || !root.is_dir())
97 {
98 errors.push(format!(
99 "[{place}] roots must name existing absolute directories"
100 ));
101 }
102 if lys.construction.is_none() {
103 errors.push(format!("[{place}] roots require [auth.lys.construction]"));
104 }
105 }
106 if let Some(section) = &lys.construction {
107 if lys.agents.is_none() {
108 errors.push("[auth.lys.construction] requires [auth.lys.agents]".into());
109 }
110 if lys.machine.is_none() {
111 errors.push("[auth.lys.construction] requires [auth.lys.machine]".into());
112 }
113 if !lys.live {
114 errors.push(
115 "[auth.lys.construction] requires auth.lys.live for deliberate drafts".into(),
116 );
117 }
118 if config.persistence_path.is_none() || lys.app_credential_path.is_none() {
119 errors.push(
120 "[auth.lys.construction] requires persistence_path and app_credential_path".into(),
121 );
122 }
123 if section.limits.snapshot_bytes.get() < section.limits.request_bytes.get() {
124 errors
125 .push("auth.lys.construction.limits.snapshot_bytes is below request_bytes".into());
126 }
127 for source in [§ion.grants.plan, §ion.grants.serve] {
128 if source.is_empty()
129 || source.len() > section.limits.text_bytes.get()
130 || source.chars().any(char::is_control)
131 {
132 errors.push(
133 "auth.lys.construction.grants must name the machine's source grants".into(),
134 );
135 }
136 }
137 }
138}
139
140impl std::fmt::Debug for ServiceTemplateConfig {
141 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 out.debug_struct("ServiceTemplateConfig")
143 .field("subjects", &self.subjects.len())
144 .field("stop_timeout_ms", &self.stop_timeout_ms)
145 .finish_non_exhaustive()
146 }
147}