use std::collections::HashMap;
use std::path::Path;
use std::str::FromStr;
use liquid_json::LiquidJsonValue;
use serde_json::Value;
use wick_packet::RuntimeConfig;
use crate::config::LiquidJsonConfig;
use crate::error::ManifestError;
#[derive(Debug, Clone, PartialEq, property::Property, serde::Serialize)]
pub struct TemplateConfig<V>
where
V: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + FromStr,
{
#[property(skip)]
#[serde(skip)]
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) value: Option<V>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) template: Option<LiquidJsonValue>,
#[serde(skip)]
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) root_config: Option<RuntimeConfig>,
}
impl<T> std::hash::Hash for TemplateConfig<T>
where
T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + FromStr,
T: std::hash::Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T> Eq for TemplateConfig<T> where T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + FromStr + Eq {}
impl<T> Default for TemplateConfig<T>
where
T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + FromStr + Default,
{
fn default() -> Self {
Self {
value: Default::default(),
template: Default::default(),
root_config: Default::default(),
}
}
}
impl<T> std::fmt::Display for TemplateConfig<T>
where
T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + FromStr,
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (&self.value, &self.template) {
(Some(v), _) => write!(f, "{}", v),
(None, Some(v)) => write!(f, "{}", v.as_json()),
(None, None) => write!(f, "invalid resource, no value and no template"),
}
}
}
impl<T> TemplateConfig<T>
where
T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + FromStr,
{
#[must_use]
pub const fn new_value(value: T) -> Self {
Self {
value: Some(value),
template: None,
root_config: None,
}
}
#[must_use]
pub fn new_template(value: String) -> Self {
Self {
value: None,
template: Some(LiquidJsonValue::new(Value::String(value))),
root_config: None,
}
}
pub const fn value(&self) -> Option<&T> {
self.value.as_ref()
}
pub fn set_value(&mut self, value: T) {
self.value = Some(value);
}
#[must_use]
pub fn value_unchecked(&self) -> &T {
self.value.as_ref().unwrap()
}
pub fn unrender(&self) -> Result<String, ManifestError> {
self.template.as_ref().map_or_else(
|| {
self.value.as_ref().map_or_else(
|| Err(ManifestError::UnrenderedConfiguration(format!("{:?}", self.template))),
|value| Ok((*value).to_string()),
)
},
|template| value_to_string(template.as_json()),
)
}
pub fn render(
&self,
source: Option<&Path>,
root: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<T, crate::Error> {
if let Some(value) = &self.value {
return Ok(value.clone());
}
let base = source.map(|source| {
let dirname = source.parent().unwrap_or_else(|| Path::new("<unavailable>"));
serde_json::json!({"__dirname": dirname})
});
let ctx = LiquidJsonConfig::make_context(base, root, None, env, None)?;
if let Some(template) = &self.template {
let rendered = template
.render(&ctx)
.map_err(|e| crate::Error::ConfigurationTemplate(e.to_string()))?;
let rendered = value_to_string(&rendered)?;
Ok(rendered.parse::<T>().map_err(|_| {
crate::Error::ConfigurationTemplate(format!(
"could not convert {} into {}",
rendered,
std::any::type_name::<T>()
))
})?)
} else {
Err(crate::Error::ConfigurationTemplate(
"No value or template specified".to_owned(),
))
}
}
}
fn value_to_string(value: &Value) -> Result<String, ManifestError> {
match value {
serde_json::Value::String(v) => Ok(v.clone()),
serde_json::Value::Number(v) => Ok(v.to_string()),
serde_json::Value::Null => Ok(String::new()),
serde_json::Value::Bool(v) => Ok(v.to_string()),
serde_json::Value::Array(_) => Err(ManifestError::TemplateStructure),
serde_json::Value::Object(_) => Err(ManifestError::TemplateStructure),
}
}
pub(crate) trait Renderable {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError>;
}
impl<T> Renderable for Option<T>
where
T: Renderable,
{
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
if let Some(v) = self {
v.render_config(source, root_config, env)?;
}
Ok(())
}
}
impl<T> Renderable for Vec<T>
where
T: Renderable,
{
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
for el in self.iter_mut() {
el.render_config(source, root_config, env)?;
}
Ok(())
}
}