use crate::render::TemplateContext;
use futures::{FutureExt, future};
use serde::Serialize;
use slumber_template::{
RenderError, RenderedChunks, Template, Value, ValueStream,
};
#[derive(Clone, Debug, derive_more::From, PartialEq, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum ValueTemplate {
Null,
Boolean(bool),
Integer(i64),
Float(f64),
String(Template),
#[from(ignore)]
Array(Vec<Self>),
#[from(ignore)]
#[serde(serialize_with = "slumber_util::serialize_mapping")]
#[cfg_attr(
feature = "schema",
schemars(with = "std::collections::HashMap<Template, Self>")
)]
Object(Vec<(Template, Self)>),
}
impl ValueTemplate {
pub fn raw(template: String) -> Self {
Self::String(Template::raw(template))
}
pub fn is_dynamic(&self) -> bool {
match self {
Self::Null
| Self::Boolean(_)
| Self::Integer(_)
| Self::Float(_) => false,
Self::String(template) => template.is_dynamic(),
Self::Array(array) => array.iter().any(Self::is_dynamic),
Self::Object(object) => object
.iter()
.any(|(key, value)| key.is_dynamic() || value.is_dynamic()),
}
}
pub async fn render_chunks(
&self,
context: &TemplateContext,
) -> RenderedChunks<Value> {
self.render_chunks_inner(context, Template::render_chunks)
.await
}
pub async fn render_chunks_stream(
&self,
context: &TemplateContext,
) -> RenderedChunks<ValueStream> {
self.render_chunks_inner(context, Template::render_chunks_stream)
.await
}
async fn render_chunks_inner<V>(
&self,
context: &TemplateContext,
render_string: impl AsyncFn(
&Template,
&TemplateContext,
) -> RenderedChunks<V>,
) -> RenderedChunks<V>
where
V: From<Value>,
{
match self {
Self::Null => Value::Null.into(),
Self::Boolean(b) => Value::Boolean(*b).into(),
Self::Integer(i) => Value::Integer(*i).into(),
Self::Float(f) => Value::Float(*f).into(),
Self::String(template) => render_string(template, context).await,
Self::Array(array) => {
future::try_join_all(array.iter().map(|value| {
value
.render_chunks(context)
.map(RenderedChunks::try_into_value)
}))
.await
.map(Value::from)
.into() }
Self::Object(map) => {
future::try_join_all(map.iter().map(|(key, value)| async {
let key = key.render_string(context).await?;
let value =
value.render_chunks(context).await.try_into_value()?;
Ok::<_, RenderError>((key, value))
}))
.await
.map(Value::from)
.into() }
}
}
}
#[cfg(any(test, feature = "test"))]
impl From<&str> for ValueTemplate {
fn from(value: &str) -> Self {
let template = value.parse().unwrap();
Self::String(template)
}
}
#[cfg(any(test, feature = "test"))]
impl<T: Into<ValueTemplate>> From<Vec<T>> for ValueTemplate {
fn from(value: Vec<T>) -> Self {
Self::Array(value.into_iter().map(T::into).collect())
}
}
#[cfg(any(test, feature = "test"))]
impl<T: Into<ValueTemplate>> From<Vec<(&str, T)>> for ValueTemplate {
fn from(value: Vec<(&str, T)>) -> Self {
Self::Object(
value
.into_iter()
.map(|(k, v)| (k.parse().unwrap(), v.into()))
.collect(),
)
}
}