1use crate::bindings::attributes::AttributesBindingAdapter;
6use crate::context::attributes::AttributesContext;
7use crate::context::authentication::AuthenticationContext;
8use crate::context::input::InputContext;
9use crate::context::merged::MergedContext;
10use crate::context::payload::PayloadContext;
11use crate::context::reference::TransientContext;
12use crate::context::vars::VarsContext;
13use crate::context::StreamSolver;
14use crate::value::IntoValue;
15use crate::{AttributesBinding, AuthenticationBinding, Format, PayloadBinding, Value};
16use pel::expression::Expression as PelExpression;
17use pel::runtime::value::Value as PelValue;
18use pel::runtime::{Context, Evaluation, Runtime, RuntimeError};
19use std::cell::RefCell;
20use std::collections::HashMap;
21use std::convert::TryFrom;
22use thiserror::Error;
23
24#[cfg(feature = "stream_body")]
25use classy::hl::BodyStream;
26
27#[allow(unused)]
29use crate::Script;
30
31thread_local! {
32 static RUNTIME: Runtime = Runtime::new();
35}
36
37pub struct Evaluator<'a> {
39 stream_solver: StreamSolver,
40 evaluation: Option<PelExpression>,
41 result: Option<PelValue>,
42 error: Option<RuntimeError>,
43
44 input_context: &'a InputContext,
45 transient_context: TransientContext,
46 vars: Option<HashMap<String, Value>>,
47
48 content_type: RefCell<Option<Format>>,
49}
50
51impl<'a> Evaluator<'a> {
52 pub(crate) fn new(
53 stream_solver: StreamSolver,
54 evaluation: Option<PelExpression>,
55 result: Option<PelValue>,
56 context: &'a InputContext,
57 ) -> Self {
58 Self {
59 stream_solver,
60 evaluation,
61 result,
62 error: None,
63 input_context: context,
64 transient_context: TransientContext::default(),
65 vars: (!context.vars().is_empty()).then(HashMap::new),
66 content_type: RefCell::new(None),
67 }
68 }
69
70 pub fn bind_vars<K: IntoValue>(&mut self, name: &str, value: K) {
73 if !self.is_ready() {
74 let vars = self
75 .vars
76 .as_mut()
77 .expect("variables are only bound by scripts that declare them");
78 vars.insert(name.to_string(), value.into_value());
79 if self
80 .input_context
81 .vars()
82 .iter()
83 .all(|item| vars.contains_key(item))
84 {
85 let vars = std::mem::take(vars);
86 self.do_eval(&VarsContext::new(self.input_context, vars));
87 }
88 }
89 }
90
91 pub fn bind_payload(&mut self, binding: &dyn PayloadBinding) {
93 if !self.is_ready() {
94 let content_type = *self.content_type.borrow();
95 self.do_eval(&PayloadContext::new(
96 self.input_context,
97 binding,
98 content_type,
99 ))
100 }
101 }
102
103 #[cfg(feature = "stream_body")]
104 pub async fn bind_stream_body(&mut self, body: BodyStream<'_>) {
106 if !self.is_ready() {
107 let content_type = *self.content_type.borrow();
108
109 let body = self.stream_solver.bind_body_stream(body).await;
110
111 self.do_eval(&PayloadContext::new(
112 self.input_context,
113 &body,
114 content_type,
115 ))
116 }
117 }
118
119 pub fn bind_attributes(&mut self, binding: &dyn AttributesBinding) {
121 if !self.is_ready() {
122 if !self.input_context.format().is_empty() {
126 Self::extract_format(binding)
127 .map(|content_type| self.content_type.borrow_mut().replace(content_type));
128
129 self.stream_solver.bind_attributes(binding);
130 }
131 let adapter = AttributesBindingAdapter::new(binding);
132 self.do_eval(&AttributesContext::new(self.input_context, &adapter))
133 }
134 }
135
136 pub fn bind_authentication(&mut self, binding: &dyn AuthenticationBinding) {
138 if !self.is_ready() {
139 self.do_eval(&AuthenticationContext::new(self.input_context, binding))
140 }
141 }
142
143 fn extract_format(binding: &dyn AttributesBinding) -> Option<Format> {
144 binding.extract_header("content-type").and_then(|header| {
145 if header.contains("/xml") {
146 Some(Format::Xml)
147 } else if header.contains("/json") {
148 Some(Format::Json)
149 } else if header.contains("text/plain") {
150 Some(Format::PlainText)
151 } else {
152 None
153 }
154 })
155 }
156
157 fn do_eval(&mut self, bound_context: &'_ dyn Context) {
158 let local_context = MergedContext::new(self.input_context, &self.transient_context);
159 let merged_context = MergedContext::new(&local_context, bound_context);
160 if let Some(expr) = self.evaluation.take() {
161 let evaluation =
162 RUNTIME.with(|runtime| runtime.eval_with_context(&expr, &merged_context));
163 match evaluation {
164 Ok(eval) => match eval {
165 Evaluation::Complete(_, v) => {
166 self.result = Some(v);
167 }
168 Evaluation::Partial(exp) => {
169 self.transient_context.detach_pending(&exp, bound_context);
170 self.evaluation = Some(exp);
171 }
172 },
173 Err(error) => {
174 self.error = Some(error);
175 }
176 }
177 }
178 }
179
180 pub fn is_ready(&self) -> bool {
182 self.result.is_some() || self.error.is_some()
183 }
184
185 pub fn eval(self) -> Result<Value, EvaluationError> {
188 if let Some(error) = self.error {
189 return Err(EvaluationError::Error(error));
190 }
191
192 self.result
193 .ok_or(EvaluationError::PartialEvaluation)
194 .and_then(|val| Value::try_from(&val))
195 }
196}
197
198#[derive(Error, Debug)]
200pub enum EvaluationError {
201 #[error("Evaluation is incomplete")]
203 PartialEvaluation,
204
205 #[error("Unsupported DataType")]
207 TypeMismatch,
208
209 #[error("Error evaluating expression: {0}")]
211 Error(RuntimeError),
212}