use crate::render::TemplateContext;
use bytes::BytesMut;
use futures::{FutureExt, TryStreamExt, future};
use serde::{Serialize, Serializer};
use slumber_template::{
Context, Expression, RenderError, RenderValue, RenderedChunk,
RenderedChunks, Template, TemplateChunk, TemplateParseError, Value,
ValueStream,
};
use std::str::FromStr;
#[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),
#[serde(serialize_with = "serialize_expression")]
#[cfg_attr(feature = "schema", schemars(with = "String"))]
Expression(Expression),
#[from(ignore)]
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::Expression(_) => true,
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_value(
&self,
context: &TemplateContext,
) -> RenderedValue<Value> {
self.render_chunks_inner(context, Template::render_chunks)
.boxed_local() .await
}
pub async fn render_value_stream(
&self,
context: &TemplateContext,
) -> RenderedValue<ValueStream> {
self.render_chunks_inner(context, Template::render_chunks_stream)
.boxed_local() .await
}
async fn render_chunks_inner<V>(
&self,
context: &TemplateContext,
render_string: impl AsyncFn(
&Template,
&TemplateContext,
) -> RenderedChunks<V>,
) -> RenderedValue<V>
where
V: RenderValue,
TemplateContext: Context<V>,
{
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::Expression(expression) => {
let result = expression.render(context).await;
RenderedValue::Value(result)
}
Self::String(template) => {
RenderedValue::Chunks(render_string(template, context).await)
}
Self::Array(array) => {
future::try_join_all(array.iter().map(|value| {
value
.render_value(context)
.map(RenderedValue::try_into_value)
}))
.await
.map(Value::Array)
.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_value(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 {
value.parse().unwrap()
}
}
#[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(),
)
}
}
#[derive(Debug)]
pub enum RenderedValue<V> {
Value(Result<V, RenderError>),
Chunks(RenderedChunks<V>),
}
impl RenderedValue<Value> {
pub fn try_into_value(self) -> Result<Value, RenderError> {
let value = match self {
RenderedValue::Value(result) => result?,
RenderedValue::Chunks(chunks) => {
let bytes = chunks.try_into_bytes()?;
Value::Bytes(bytes)
}
};
Ok(value.decode_bytes())
}
}
impl RenderedValue<ValueStream> {
pub fn has_stream(&self) -> bool {
match self {
Self::Value(Ok(ValueStream::Stream { .. })) => true,
Self::Value(Ok(ValueStream::Value(_)) | Err(_)) => false,
Self::Chunks(chunks) => chunks.iter().any(|chunk| match chunk {
RenderedChunk::Raw(_)
| RenderedChunk::Dynamic(ValueStream::Value(_))
| RenderedChunk::Error(_) => false,
RenderedChunk::Dynamic(ValueStream::Stream { .. }) => true,
}),
}
}
pub async fn try_collect_value(self) -> Result<Value, RenderError> {
let value = match self {
Self::Value(Ok(ValueStream::Value(value))) => value,
Self::Value(Ok(stream @ ValueStream::Stream { .. })) => {
stream.resolve().await?
}
Self::Value(Err(error)) => return Err(error),
Self::Chunks(chunks) => {
let bytes = chunks
.try_into_stream()?
.try_collect::<BytesMut>()
.await?
.into();
Value::Bytes(bytes)
}
};
Ok(value.decode_bytes())
}
}
impl<V: RenderValue> From<Value> for RenderedValue<V> {
fn from(value: Value) -> Self {
Self::Value(Ok(V::from_value(value)))
}
}
impl<V: RenderValue> From<Result<Value, RenderError>> for RenderedValue<V> {
fn from(result: Result<Value, RenderError>) -> Self {
Self::Value(result.map(V::from_value))
}
}
impl From<Template> for ValueTemplate {
fn from(template: Template) -> Self {
match <[_; 1]>::try_from(template.into_chunks()) {
Ok([TemplateChunk::Expression(expression)]) => {
Self::Expression(expression)
}
Ok(chunks) => Self::String(Template::from_chunks(chunks.into())),
Err(chunks) => Self::String(Template::from_chunks(chunks)),
}
}
}
impl FromStr for ValueTemplate {
type Err = TemplateParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let template: Template = s.parse()?;
Ok(template.into())
}
}
fn serialize_expression<S: Serializer>(
expression: &Expression,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&Template::display_expression(expression))
}