Skip to main content

pdk_script/
evaluator.rs

1// Copyright 2023 Salesforce, Inc. All rights reserved.
2
3use crate::bindings::attributes::AttributesBindingAdapter;
4use crate::context::attributes::AttributesContext;
5use crate::context::authentication::AuthenticationContext;
6use crate::context::input::InputContext;
7use crate::context::merged::MergedContext;
8use crate::context::payload::PayloadContext;
9use crate::context::reference::TransientContext;
10use crate::context::vars::VarsContext;
11use crate::value::IntoValue;
12use crate::{AttributesBinding, AuthenticationBinding, PayloadBinding, Value};
13use pel::expression::Expression as PelExpression;
14use pel::runtime::value::Value as PelValue;
15use pel::runtime::{Context, Evaluation, Runtime, RuntimeError};
16use std::collections::HashMap;
17use std::convert::TryFrom;
18use thiserror::Error;
19
20// For rustdoc.
21#[allow(unused)]
22use crate::Script;
23
24/// An evaluator for [`Script`]s.
25pub struct Evaluator<'a> {
26    evaluation: Option<PelExpression>,
27    result: Option<PelValue>,
28    error: Option<RuntimeError>,
29
30    input_context: &'a InputContext,
31    transient_context: TransientContext,
32    vars: HashMap<String, Value>,
33}
34
35impl<'a> Evaluator<'a> {
36    pub(crate) fn new(
37        evaluation: Option<PelExpression>,
38        result: Option<PelValue>,
39        context: &'a InputContext,
40    ) -> Self {
41        Self {
42            evaluation,
43            result,
44            error: None,
45            input_context: context,
46            transient_context: TransientContext::default(),
47            vars: HashMap::new(),
48        }
49    }
50
51    /// Binds a set of values to a variable indentified by the `name` argument,
52    /// nested into the `vars` namespace.
53    pub fn bind_vars<K: IntoValue>(&mut self, name: &str, value: K) {
54        if !self.is_ready() {
55            self.vars.insert(name.to_string(), value.into_value());
56            if self
57                .input_context
58                .vars()
59                .iter()
60                .all(|item| self.vars.contains_key(item))
61            {
62                let vars = self.vars.drain().collect();
63                self.do_eval(&VarsContext::new(self.input_context, vars));
64            }
65        }
66    }
67
68    /// Binds a payload to the `payload` variable.
69    pub fn bind_payload(&mut self, binding: &dyn PayloadBinding) {
70        if !self.is_ready() {
71            self.do_eval(&PayloadContext::new(self.input_context, binding))
72        }
73    }
74
75    /// Binds a set of actual attributes to the `attributes` namespace.
76    pub fn bind_attributes(&mut self, binding: &dyn AttributesBinding) {
77        if !self.is_ready() {
78            let adapter = AttributesBindingAdapter::new(binding);
79            self.do_eval(&AttributesContext::new(self.input_context, &adapter))
80        }
81    }
82
83    /// Binds a set of actual authentication properties to the `authentication` namespace.
84    pub fn bind_authentication(&mut self, binding: &dyn AuthenticationBinding) {
85        if !self.is_ready() {
86            self.do_eval(&AuthenticationContext::new(self.input_context, binding))
87        }
88    }
89
90    fn do_eval(&mut self, bound_context: &'_ dyn Context) {
91        let local_context = MergedContext::new(self.input_context, &self.transient_context);
92        let merged_context = MergedContext::new(&local_context, bound_context);
93        if let Some(expr) = self.evaluation.take() {
94            match Runtime::new().eval_with_context(&expr, &merged_context) {
95                Ok(eval) => match eval {
96                    Evaluation::Complete(_, v) => {
97                        self.result = Some(v);
98                    }
99                    Evaluation::Partial(exp) => {
100                        self.transient_context.detach_pending(&exp, bound_context);
101                        self.evaluation = Some(exp);
102                    }
103                },
104                Err(error) => {
105                    self.error = Some(error);
106                }
107            }
108        }
109    }
110
111    /// Returns `[true]` if the Evaluator is ready to be evaluated.
112    pub fn is_ready(&self) -> bool {
113        self.result.is_some() || self.error.is_some()
114    }
115
116    /// Evals the script with the given bindings and returns the resulting [`Value`].
117    /// Returns an [`EvaluationError`] when errors occurs during the evaluation.
118    pub fn eval(self) -> Result<Value, EvaluationError> {
119        if let Some(error) = self.error {
120            return Err(EvaluationError::Error(error));
121        }
122
123        self.result
124            .ok_or(EvaluationError::PartialEvaluation)
125            .and_then(|val| Value::try_from(&val))
126    }
127}
128
129/// Represents an evaluation error.
130#[derive(Error, Debug)]
131pub enum EvaluationError {
132    /// Represents an incomplete evaluation error.
133    #[error("Evaluation is incomplete")]
134    PartialEvaluation,
135
136    /// Represents a type mismatch error.
137    #[error("Unsupported DataType")]
138    TypeMismatch,
139
140    /// Represents a runtime error.
141    #[error("Error evaluating expression: {0}")]
142    Error(RuntimeError),
143}