use crate::bindings::attributes::AttributesBindingAdapter;
use crate::context::attributes::AttributesContext;
use crate::context::authentication::AuthenticationContext;
use crate::context::input::InputContext;
use crate::context::merged::MergedContext;
use crate::context::payload::PayloadContext;
use crate::context::reference::TransientContext;
use crate::context::vars::VarsContext;
use crate::context::StreamSolver;
use crate::value::IntoValue;
use crate::{AttributesBinding, AuthenticationBinding, Format, PayloadBinding, Value};
use pel::expression::Expression as PelExpression;
use pel::runtime::value::Value as PelValue;
use pel::runtime::{Context, Evaluation, Runtime, RuntimeError};
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use thiserror::Error;
#[cfg(feature = "stream_body")]
use classy::hl::BodyStream;
#[allow(unused)]
use crate::Script;
pub struct Evaluator<'a> {
stream_solver: StreamSolver,
evaluation: Option<PelExpression>,
result: Option<PelValue>,
error: Option<RuntimeError>,
input_context: &'a InputContext,
transient_context: TransientContext,
vars: HashMap<String, Value>,
content_type: RefCell<Option<Format>>,
}
impl<'a> Evaluator<'a> {
pub(crate) fn new(
stream_solver: StreamSolver,
evaluation: Option<PelExpression>,
result: Option<PelValue>,
context: &'a InputContext,
) -> Self {
Self {
stream_solver,
evaluation,
result,
error: None,
input_context: context,
transient_context: TransientContext::default(),
vars: HashMap::new(),
content_type: RefCell::new(None),
}
}
pub fn bind_vars<K: IntoValue>(&mut self, name: &str, value: K) {
if !self.is_ready() {
self.vars.insert(name.to_string(), value.into_value());
if self
.input_context
.vars()
.iter()
.all(|item| self.vars.contains_key(item))
{
let vars = self.vars.drain().collect();
self.do_eval(&VarsContext::new(self.input_context, vars));
}
}
}
pub fn bind_payload(&mut self, binding: &dyn PayloadBinding) {
if !self.is_ready() {
let content_type = *self.content_type.borrow();
self.do_eval(&PayloadContext::new(
self.input_context,
binding,
content_type,
))
}
}
#[cfg(feature = "stream_body")]
pub async fn bind_stream_body(&mut self, body: BodyStream<'_>) {
if !self.is_ready() {
let content_type = *self.content_type.borrow();
let body = self.stream_solver.bind_body_stream(body).await;
self.do_eval(&PayloadContext::new(
self.input_context,
&body,
content_type,
))
}
}
pub fn bind_attributes(&mut self, binding: &dyn AttributesBinding) {
if !self.is_ready() {
Self::extract_format(binding)
.map(|content_type| self.content_type.borrow_mut().replace(content_type));
self.stream_solver.bind_attributes(binding);
let adapter = AttributesBindingAdapter::new(binding);
self.do_eval(&AttributesContext::new(self.input_context, &adapter))
}
}
pub fn bind_authentication(&mut self, binding: &dyn AuthenticationBinding) {
if !self.is_ready() {
self.do_eval(&AuthenticationContext::new(self.input_context, binding))
}
}
fn extract_format(binding: &dyn AttributesBinding) -> Option<Format> {
binding.extract_header("content-type").and_then(|header| {
if header.contains("/xml") {
Some(Format::Xml)
} else if header.contains("/json") {
Some(Format::Json)
} else if header.contains("text/plain") {
Some(Format::PlainText)
} else {
None
}
})
}
fn do_eval(&mut self, bound_context: &'_ dyn Context) {
let local_context = MergedContext::new(self.input_context, &self.transient_context);
let merged_context = MergedContext::new(&local_context, bound_context);
if let Some(expr) = self.evaluation.take() {
match Runtime::new().eval_with_context(&expr, &merged_context) {
Ok(eval) => match eval {
Evaluation::Complete(_, v) => {
self.result = Some(v);
}
Evaluation::Partial(exp) => {
self.transient_context.detach_pending(&exp, bound_context);
self.evaluation = Some(exp);
}
},
Err(error) => {
self.error = Some(error);
}
}
}
}
pub fn is_ready(&self) -> bool {
self.result.is_some() || self.error.is_some()
}
pub fn eval(self) -> Result<Value, EvaluationError> {
if let Some(error) = self.error {
return Err(EvaluationError::Error(error));
}
self.result
.ok_or(EvaluationError::PartialEvaluation)
.and_then(|val| Value::try_from(&val))
}
}
#[derive(Error, Debug)]
pub enum EvaluationError {
#[error("Evaluation is incomplete")]
PartialEvaluation,
#[error("Unsupported DataType")]
TypeMismatch,
#[error("Error evaluating expression: {0}")]
Error(RuntimeError),
}