use super::TemplatePolicy;
use crate::config::types::ServiceTemplateConfig;
use serde::{Deserialize, Deserializer};
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct TemplateSources {
configured: Vec<ServiceTemplateConfig>,
pub policy: Option<TemplatePolicy>,
}
impl TemplateSources {
#[must_use]
pub fn is_empty(&self) -> bool {
self.configured.is_empty() && self.policy.is_none()
}
}
impl<'de> Deserialize<'de> for TemplateSources {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Table {
#[serde(default)]
configured: Vec<ServiceTemplateConfig>,
roots: Vec<std::path::PathBuf>,
command_bytes: std::num::NonZeroU64,
records: std::num::NonZeroUsize,
arguments: std::num::NonZeroUsize,
environment_names: std::num::NonZeroUsize,
text_bytes: std::num::NonZeroUsize,
stop_timeout_ms: std::num::NonZeroU64,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Wire {
Legacy(Vec<ServiceTemplateConfig>),
Table(Table),
}
Ok(match Wire::deserialize(deserializer)? {
Wire::Legacy(configured) => Self {
configured,
policy: None,
},
Wire::Table(table) => Self {
configured: table.configured,
policy: Some(TemplatePolicy {
roots: table.roots,
command_bytes: table.command_bytes,
records: table.records,
arguments: table.arguments,
environment_names: table.environment_names,
text_bytes: table.text_bytes,
stop_timeout_ms: table.stop_timeout_ms,
}),
},
})
}
}
impl std::ops::Deref for TemplateSources {
type Target = [ServiceTemplateConfig];
fn deref(&self) -> &Self::Target {
&self.configured
}
}
impl<'a> IntoIterator for &'a TemplateSources {
type Item = &'a ServiceTemplateConfig;
type IntoIter = std::slice::Iter<'a, ServiceTemplateConfig>;
fn into_iter(self) -> Self::IntoIter {
self.configured.iter()
}
}
impl From<Vec<ServiceTemplateConfig>> for TemplateSources {
fn from(configured: Vec<ServiceTemplateConfig>) -> Self {
Self {
configured,
policy: None,
}
}
}
pub fn collect_errors(config: &crate::config::types::ServerConfig, errors: &mut Vec<String>) {
let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
return;
};
if let Some(policy) = &lys.templates.policy {
if policy.roots.is_empty()
|| policy
.roots
.iter()
.any(|root| !root.is_absolute() || !root.is_dir())
{
errors
.push("[auth.lys.templates] roots must name existing absolute directories".into());
}
if lys.construction.is_none() {
errors.push("[auth.lys.templates] roots require [auth.lys.construction]".into());
}
}
if let Some(section) = &lys.construction {
if lys.agents.is_none() {
errors.push("[auth.lys.construction] requires [auth.lys.agents]".into());
}
if lys.machine.is_none() {
errors.push("[auth.lys.construction] requires [auth.lys.machine]".into());
}
if !lys.live {
errors.push(
"[auth.lys.construction] requires auth.lys.live for deliberate drafts".into(),
);
}
if config.persistence_path.is_none() || lys.app_credential_path.is_none() {
errors.push(
"[auth.lys.construction] requires persistence_path and app_credential_path".into(),
);
}
if section.limits.snapshot_bytes.get() < section.limits.request_bytes.get() {
errors
.push("auth.lys.construction.limits.snapshot_bytes is below request_bytes".into());
}
for source in [§ion.grants.plan, §ion.grants.serve] {
if source.is_empty()
|| source.len() > section.limits.text_bytes.get()
|| source.chars().any(char::is_control)
{
errors.push(
"auth.lys.construction.grants must name the machine's source grants".into(),
);
}
}
}
}
impl std::fmt::Debug for ServiceTemplateConfig {
fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
out.debug_struct("ServiceTemplateConfig")
.field("subjects", &self.subjects.len())
.field("stop_timeout_ms", &self.stop_timeout_ms)
.finish_non_exhaustive()
}
}