#[cfg(test)]
use crate::test_util;
use crate::{
Arguments, Context, RenderError, RenderValue, Value,
error::RenderErrorContext,
};
use bytes::Bytes;
use derive_more::{Deref, Display, From};
use futures::{
FutureExt, TryFutureExt,
future::{self, try_join},
};
use indexmap::IndexMap;
use std::borrow::Cow;
type RenderResult<V> = Result<V, RenderError>;
#[derive(Clone, Debug, PartialEq)]
pub enum Expression {
Literal(Literal),
Field(Identifier),
Array(Vec<Self>),
Object(Vec<(Self, Self)>),
Call(FunctionCall),
Pipe {
expression: Box<Self>,
call: FunctionCall,
},
}
impl Expression {
pub async fn render<Ctx, V>(&self, context: &Ctx) -> RenderResult<V>
where
Ctx: Context<V>,
V: RenderValue,
{
match self {
Self::Literal(literal) => Ok(V::from_value(literal.into())),
Self::Array(expressions) => {
let values = future::try_join_all(
expressions
.iter()
.map(|expression| expression.render_value(context)),
)
.boxed_local() .await?;
Ok(V::from_value(Value::Array(values)))
}
Self::Object(entries) => {
let pairs: Vec<(String, Value)> =
future::try_join_all(entries.iter().map(|(key, value)| {
let key_future = async move {
let key = key.render_value(context).await?;
key.try_into_string().map_err(|error| {
RenderError::Value(error.error)
})
};
try_join(key_future, value.render_value(context))
}))
.boxed_local() .await?;
Ok(V::from_value(Value::Object(IndexMap::from_iter(pairs))))
}
Self::Field(field) => context.get_field(field).await,
Self::Call(call) => call.call(context, None).await,
Self::Pipe { expression, call } => {
let value =
expression.render_value(context).boxed_local().await?;
call.call(context, Some(value)).await
}
}
}
async fn render_value<Ctx, V>(&self, context: &Ctx) -> RenderResult<Value>
where
Ctx: Context<V>,
V: RenderValue,
{
self.render(context).and_then(V::try_resolve_stream).await
}
pub fn call(
function_name: &'static str,
position: impl IntoIterator<Item = Expression>,
keyword: impl IntoIterator<Item = (&'static str, Option<Expression>)>,
) -> Self {
Self::Call(FunctionCall::new(function_name, position, keyword))
}
#[must_use]
pub fn pipe(
self,
rhs_name: &'static str,
rhs_position: impl IntoIterator<Item = Expression>,
rhs_keyword: impl IntoIterator<Item = (&'static str, Option<Expression>)>,
) -> Self {
Self::Pipe {
expression: Box::new(self),
call: FunctionCall::new(rhs_name, rhs_position, rhs_keyword),
}
}
}
impl From<bool> for Expression {
fn from(b: bool) -> Self {
Self::Literal(Literal::Boolean(b))
}
}
impl From<f64> for Expression {
fn from(f: f64) -> Self {
Self::Literal(Literal::Float(f))
}
}
impl From<i64> for Expression {
fn from(i: i64) -> Self {
Self::Literal(Literal::Integer(i))
}
}
impl From<Literal> for Expression {
fn from(literal: Literal) -> Self {
Self::Literal(literal)
}
}
impl From<String> for Expression {
fn from(value: String) -> Self {
Self::Literal(Literal::from(value))
}
}
impl From<&str> for Expression {
fn from(value: &str) -> Self {
Self::Literal(Literal::from(value))
}
}
impl From<Cow<'_, str>> for Expression {
fn from(value: Cow<'_, str>) -> Self {
Self::Literal(Literal::from(value.into_owned()))
}
}
impl From<&'static [u8]> for Expression {
fn from(value: &'static [u8]) -> Self {
Self::Literal(Literal::Bytes(Bytes::from(value.to_vec())))
}
}
impl<const N: usize> From<&'static [u8; N]> for Expression {
fn from(value: &'static [u8; N]) -> Self {
value.as_slice().into()
}
}
impl From<Vec<Expression>> for Expression {
fn from(values: Vec<Expression>) -> Self {
Self::Array(values)
}
}
impl From<Vec<i64>> for Expression {
fn from(values: Vec<i64>) -> Self {
values.into_iter().map(Expression::from).collect()
}
}
impl From<Vec<&str>> for Expression {
fn from(values: Vec<&str>) -> Self {
values.into_iter().map(Expression::from).collect()
}
}
impl<const N: usize> From<[(&str, Expression); N]> for Expression {
fn from(value: [(&str, Expression); N]) -> Self {
Self::Object(value.into_iter().map(|(k, v)| (k.into(), v)).collect())
}
}
impl FromIterator<Expression> for Expression {
fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
Self::Array(Vec::from_iter(iter))
}
}
impl<T> From<Option<T>> for Expression
where
Expression: From<T>,
{
fn from(value: Option<T>) -> Self {
value
.map(Expression::from)
.unwrap_or(Expression::Literal(Literal::Null))
}
}
#[derive(Clone, Debug, From, PartialEq)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub enum Literal {
Null,
Boolean(bool),
Integer(i64),
Float(f64),
String(String),
Bytes(#[cfg_attr(test, proptest(strategy = "test_util::bytes()"))] Bytes),
}
impl From<&str> for Literal {
fn from(value: &str) -> Self {
Self::String(value.to_owned())
}
}
impl<const N: usize> From<&[u8; N]> for Literal {
fn from(value: &[u8; N]) -> Self {
Self::Bytes(Bytes::from(value.to_vec()))
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct FunctionCall {
pub(crate) function: Identifier,
#[cfg_attr(
test,
proptest(
strategy = "proptest::collection::vec(test_util::expression_arbitrary(), 0..=3)"
)
)]
pub(crate) position: Vec<Expression>,
#[cfg_attr(
test,
proptest(
strategy = "test_util::index_map(Identifier::arbitrary(), test_util::expression_arbitrary(), 0..=3)"
)
)]
pub(crate) keyword: IndexMap<Identifier, Expression>,
}
impl FunctionCall {
fn new(
function_name: &'static str,
position: impl IntoIterator<Item = Expression>,
keyword: impl IntoIterator<Item = (&'static str, Option<Expression>)>,
) -> Self {
FunctionCall {
function: function_name.into(),
position: position.into_iter().collect(),
keyword: keyword
.into_iter()
.filter_map(|(name, value)| Some((name.into(), value?)))
.collect(),
}
}
async fn call<Ctx, V>(
&self,
context: &Ctx,
piped_argument: Option<Value>,
) -> RenderResult<V>
where
Ctx: Context<V>,
V: RenderValue,
{
let map_error = |error: RenderError| {
error.context(RenderErrorContext::Function(self.function.clone()))
};
let mut arguments =
self.render_arguments(context).await.map_err(map_error)?;
if let Some(piped_argument) = piped_argument {
arguments.push_piped(piped_argument);
}
context
.call(&self.function, arguments)
.await
.map_err(map_error)
}
async fn render_arguments<'ctx, Ctx, V>(
&self,
context: &'ctx Ctx,
) -> Result<Arguments<'ctx, Ctx>, RenderError>
where
Ctx: Context<V>,
V: RenderValue,
{
let position_future =
future::try_join_all(self.position.iter().enumerate().map(
|(index, expression)| async move {
expression.render_value(context).await.map_err(|error| {
error.context(RenderErrorContext::ArgumentRender {
argument: index.to_string(),
expression: expression.clone(),
})
})
},
));
let keyword_future = future::try_join_all(self.keyword.iter().map(
|(name, expression)| async {
let value = expression.render_value(context).await.map_err(
|error| {
error.context(RenderErrorContext::ArgumentRender {
argument: name.to_string(),
expression: expression.clone(),
})
},
)?;
Ok((name.to_string(), value))
},
));
let (position, keyword) =
future::try_join(position_future, keyword_future)
.boxed_local() .await?;
Ok(Arguments::new(
context,
position.into(),
keyword.into_iter().collect(),
))
}
}
#[derive(Clone, Debug, Deref, Default, Display, Eq, Hash, PartialEq)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct Identifier(
#[cfg_attr(test, proptest(regex = r"[\p{L}_][\p{L}0-9-_]*"))]
pub(crate) String,
);
impl From<&'static str> for Identifier {
fn from(value: &'static str) -> Self {
Self(value.parse().unwrap())
}
}