use crate::config::{
AppConfig, ArtifactsConfig, CaddyfileConfig, SystemdUnitConfig, TemplateConfig, TemplateOrigin,
UserVar,
};
use crate::util::labeled_span;
use miette::{LabeledSpan, Report, SourceCode, miette};
use std::collections::HashMap;
use std::path::PathBuf;
use toml_span::de_helpers::{TableHelper, expected};
use toml_span::value::ValueInner;
use toml_span::{DeserError, Deserialize, Error, ErrorKind, Spanned, Value};
impl<'de> Deserialize<'de> for AppConfig {
fn deserialize(value: &mut Value<'de>) -> Result<Self, DeserError> {
let mut th = TableHelper::new(value)?;
let artifacts = th.optional_s("artifacts");
let caddyfile = th.optional_s("caddyfile");
let systemd_units = th.optional("systemd-unit").unwrap_or_default();
th.finalize(None)?;
Ok(AppConfig {
artifacts,
caddyfile,
systemd_units,
})
}
}
impl<'de> Deserialize<'de> for ArtifactsConfig {
fn deserialize(value: &mut Value<'de>) -> Result<Self, DeserError> {
let mut th = TableHelper::new(value)?;
let path = th.required_s::<String>("path");
th.finalize(None)?;
Ok(ArtifactsConfig { path: path?.map() })
}
}
impl<'de> Deserialize<'de> for TemplateConfig {
fn deserialize(value: &mut Value<'de>) -> Result<Self, DeserError> {
let span = value.span;
let mut th = TableHelper::new(value)?;
let builtin = th.optional_s::<String>("built-in");
let file = th.optional_s::<String>("file");
let inline = th.optional_s::<String>("inline");
let origin = match (builtin, file, inline) {
(Some(b), None, None) => {
Some(Spanned::with_span(TemplateOrigin::Builtin(b.value), b.span))
}
(None, Some(f), None) => Some(Spanned::with_span(
TemplateOrigin::File(PathBuf::from(f.value)),
f.span,
)),
(None, None, Some(i)) => {
let span = i.span;
Some(Spanned::with_span(TemplateOrigin::Inline(i), span))
}
(None, None, None) => {
th.errors.push(Error {
kind: ErrorKind::Custom(
"missing template source: set exactly one of `built-in`, `file`, or `inline`"
.into(),
),
span,
line_info: None,
});
None
}
(b, f, i) => {
for present in [b.map(|x| x.span), f.map(|x| x.span), i.map(|x| x.span)]
.into_iter()
.flatten()
{
th.errors.push(Error {
kind: ErrorKind::Custom(
"conflicting template source: set only one of `built-in`, `file`, or `inline`"
.into(),
),
span: present,
line_info: None,
});
}
None
}
};
let vars = deserialize_vars_table(&mut th);
th.finalize(None)?;
Ok(TemplateConfig {
origin: origin.unwrap(), vars,
})
}
}
impl<'de> Deserialize<'de> for SystemdUnitConfig {
fn deserialize(value: &mut Value<'de>) -> Result<Self, DeserError> {
let mut th = TableHelper::new(value)?;
let filename_suffix = th.optional_s("filename-suffix");
let file_extension = th.optional_s("file-extension");
let template = th.required_s("template");
th.finalize(None)?;
Ok(SystemdUnitConfig {
filename_suffix,
file_extension,
template: template?,
})
}
}
impl<'de> Deserialize<'de> for CaddyfileConfig {
fn deserialize(value: &mut Value<'de>) -> Result<Self, DeserError> {
let mut th = TableHelper::new(value)?;
let template = th.required_s("template");
th.finalize(None)?;
Ok(CaddyfileConfig {
template: template.expect("template is set when there are no errors"),
})
}
}
pub fn toml_error_to_report<S: SourceCode + 'static>(err: DeserError, source: S) -> Report {
let mut labels = Vec::new();
for e in &err.errors {
labels.extend(error_labels(e));
}
miette!(labels = labels, "failed to parse vade config file").with_source_code(source)
}
fn error_labels(e: &Error) -> Vec<LabeledSpan> {
match &e.kind {
ErrorKind::UnexpectedKeys { keys, .. } => keys
.iter()
.map(|(name, span)| labeled_span(format!("unexpected key `{name}`"), *span))
.collect(),
ErrorKind::DuplicateKey { first, .. } | ErrorKind::DuplicateTable { first, .. } => vec![
labeled_span(e.to_string(), e.span),
labeled_span("first defined here".to_string(), *first),
],
_ => vec![labeled_span(e.to_string(), e.span)],
}
}
fn deserialize_vars_table(th: &mut TableHelper) -> Spanned<HashMap<String, UserVar>> {
let Some((_, mut value)) = th.take("vars") else {
return Spanned::new(HashMap::new());
};
let span = value.span;
match value.take() {
ValueInner::Table(table) => Spanned::with_span(
table
.into_iter()
.map(|(k, v)| (k.name.into_owned(), UserVar::from_toml(v)))
.collect(),
span,
),
other => {
th.errors.push(expected("a table", other, span));
Spanned::with_span(HashMap::new(), span)
}
}
}