Skip to main content

zen_engine/nodes/
context.rs

1use crate::nodes::definition::{NodeDataType, TraceDataType};
2use crate::nodes::extensions::NodeHandlerExtensions;
3use crate::nodes::function::v2::function::Function;
4use crate::nodes::result::{NodeResponse, NodeResult};
5use crate::nodes::variable_json::{Guards, VariableNode};
6use crate::nodes::NodeError;
7use crate::ZEN_CONFIG;
8use ahash::AHasher;
9use jsonschema::ValidationError;
10use serde::Serialize;
11use serde_json::Value;
12use std::cell::RefCell;
13use std::fmt::{Display, Formatter};
14use std::hash::Hasher;
15use std::sync::atomic::Ordering;
16use std::sync::Arc;
17use thiserror::Error;
18use zen_expression::Isolate;
19use zen_types::variable::{ToVariable, Variable};
20
21#[derive(Clone)]
22pub struct NodeContext<NodeData, TraceData>
23where
24    NodeData: NodeDataType,
25    TraceData: TraceDataType,
26{
27    pub id: Arc<str>,
28    pub name: Arc<str>,
29    pub node: NodeData,
30    pub input: Variable,
31    pub nodes: Option<Variable>,
32    pub trace: Option<RefCell<TraceData>>,
33    pub extensions: NodeHandlerExtensions,
34    pub iteration: u8,
35    pub config: NodeContextConfig,
36}
37
38impl<NodeData, TraceData> NodeContext<NodeData, TraceData>
39where
40    NodeData: NodeDataType,
41    TraceData: TraceDataType,
42{
43    pub fn input_with_nodes(&self) -> Variable {
44        let Some(nodes) = &self.nodes else {
45            return self.input.shallow_clone();
46        };
47        let Variable::Object(object) = &self.input else {
48            return self.input.shallow_clone();
49        };
50
51        let mut map = object.borrow().clone();
52        map.insert(Variable::nodes_key(), nodes.clone());
53        Variable::from_object(map)
54    }
55
56    pub fn from_base(base: NodeContextBase, data: NodeData) -> Self {
57        Self {
58            id: base.id,
59            name: base.name,
60            input: base.input,
61            nodes: base.nodes,
62            extensions: base.extensions,
63            iteration: base.iteration,
64            trace: base.config.trace.then(|| Default::default()),
65            node: data,
66            config: base.config,
67        }
68    }
69
70    pub fn isolate(&self) -> Isolate {
71        make_isolate(&self.input, self.nodes.as_ref(), &self.extensions)
72    }
73
74    pub fn trace<Function>(&self, mutator: Function)
75    where
76        Function: FnOnce(&mut TraceData),
77    {
78        if let Some(trace) = &self.trace {
79            mutator(&mut *trace.borrow_mut());
80        }
81    }
82
83    pub fn error<Error>(&self, error: Error) -> NodeResult
84    where
85        Error: Into<Box<dyn std::error::Error>>,
86    {
87        Err(self.make_error(error))
88    }
89
90    pub fn success(&self, output: Variable) -> NodeResult {
91        Ok(NodeResponse {
92            output,
93            trace_data: self.trace.as_ref().map(|v| (*v.borrow()).to_variable()),
94        })
95    }
96
97    pub(crate) fn make_error<Error>(&self, error: Error) -> NodeError
98    where
99        Error: Into<Box<dyn std::error::Error>>,
100    {
101        NodeError {
102            node_id: self.id.clone(),
103            trace: self.trace.as_ref().map(|v| (*v.borrow()).to_variable()),
104            source: error.into(),
105        }
106    }
107
108    pub(crate) async fn function_runtime(&self) -> Result<&Function, NodeError> {
109        self.extensions.function_runtime().await.node_context(self)
110    }
111
112    pub fn validate(&self, schema: &Value, value: &Variable) -> Result<(), NodeError> {
113        let validator_cache = self.extensions.validator_cache();
114        let hash = self.hash_node();
115
116        let validator = validator_cache
117            .get_or_insert(hash, schema)
118            .node_context(self)?;
119
120        let guards = Guards::default();
121        validator
122            .validate(VariableNode::new(value, &guards))
123            .map_err(|err| ValidationErrorJson::from(err))
124            .node_context(self)?;
125
126        Ok(())
127    }
128
129    fn hash_node(&self) -> u64 {
130        let mut hasher = AHasher::default();
131        hasher.write(self.id.as_bytes());
132        hasher.write(self.name.as_bytes());
133        hasher.write_u64(self.config.validation_salt);
134        hasher.finish()
135    }
136}
137
138pub trait NodeContextExt<T, Context>: Sized {
139    type Error: Into<Box<dyn std::error::Error>>;
140
141    fn with_node_context<Function, NewError>(
142        self,
143        ctx: &Context,
144        f: Function,
145    ) -> Result<T, NodeError>
146    where
147        Function: FnOnce(Self::Error) -> NewError,
148        NewError: Into<Box<dyn std::error::Error>>;
149
150    fn node_context(self, ctx: &Context) -> Result<T, NodeError> {
151        self.with_node_context(ctx, |e| e.into())
152    }
153
154    fn node_context_message(self, ctx: &Context, message: &str) -> Result<T, NodeError> {
155        self.with_node_context(ctx, |err| format!("{}: {}", message, err.into()))
156    }
157}
158
159impl<T, E, NodeData, TraceData> NodeContextExt<T, NodeContext<NodeData, TraceData>> for Result<T, E>
160where
161    E: Into<Box<dyn std::error::Error>>,
162    NodeData: NodeDataType,
163    TraceData: TraceDataType,
164{
165    type Error = E;
166
167    fn with_node_context<Function, NewError>(
168        self,
169        ctx: &NodeContext<NodeData, TraceData>,
170        f: Function,
171    ) -> Result<T, NodeError>
172    where
173        Function: FnOnce(Self::Error) -> NewError,
174        NewError: Into<Box<dyn std::error::Error>>,
175    {
176        self.map_err(|err| ctx.make_error(f(err)))
177    }
178}
179
180impl<T, NodeData, TraceData> NodeContextExt<T, NodeContext<NodeData, TraceData>> for Option<T>
181where
182    NodeData: NodeDataType,
183    TraceData: TraceDataType,
184{
185    type Error = &'static str;
186
187    fn with_node_context<Function, NewError>(
188        self,
189        ctx: &NodeContext<NodeData, TraceData>,
190        f: Function,
191    ) -> Result<T, NodeError>
192    where
193        Function: FnOnce(Self::Error) -> NewError,
194        NewError: Into<Box<dyn std::error::Error>>,
195    {
196        self.ok_or_else(|| ctx.make_error(f("None")))
197    }
198
199    fn node_context_message(
200        self,
201        ctx: &NodeContext<NodeData, TraceData>,
202        message: &str,
203    ) -> Result<T, NodeError> {
204        self.with_node_context(ctx, |_| message.to_string())
205    }
206}
207
208#[derive(Clone)]
209pub struct NodeContextBase {
210    pub id: Arc<str>,
211    pub name: Arc<str>,
212    pub input: Variable,
213    pub nodes: Option<Variable>,
214    pub iteration: u8,
215    pub extensions: NodeHandlerExtensions,
216    pub config: NodeContextConfig,
217    pub trace: Option<RefCell<Variable>>,
218}
219
220pub(crate) fn make_isolate(
221    input: &Variable,
222    nodes: Option<&Variable>,
223    extensions: &NodeHandlerExtensions,
224) -> Isolate {
225    let mut isolate =
226        Isolate::with_environment(input.clone()).with_cache(extensions.compiled_cache.clone());
227    if let Some(nodes) = nodes {
228        isolate.set_local(Variable::nodes_key(), nodes.clone());
229    }
230
231    isolate
232}
233
234impl NodeContextBase {
235    pub fn isolate(&self) -> Isolate {
236        make_isolate(&self.input, self.nodes.as_ref(), &self.extensions)
237    }
238
239    pub fn error<Error>(&self, error: Error) -> NodeResult
240    where
241        Error: Into<Box<dyn std::error::Error>>,
242    {
243        Err(self.make_error(error))
244    }
245
246    pub fn success(&self, output: Variable) -> NodeResult {
247        Ok(NodeResponse {
248            output,
249            trace_data: self.trace.as_ref().map(|v| v.borrow().to_variable()),
250        })
251    }
252
253    fn make_error<Error>(&self, error: Error) -> NodeError
254    where
255        Error: Into<Box<dyn std::error::Error>>,
256    {
257        NodeError {
258            node_id: self.id.clone(),
259            trace: self.trace.as_ref().map(|t| t.borrow().to_variable()),
260            source: error.into(),
261        }
262    }
263
264    pub fn trace<Function>(&self, mutator: Function)
265    where
266        Function: FnOnce(&mut Variable),
267    {
268        if let Some(trace) = &self.trace {
269            mutator(&mut *trace.borrow_mut());
270        }
271    }
272}
273
274impl<NodeData, TraceData> From<NodeContext<NodeData, TraceData>> for NodeContextBase
275where
276    NodeData: NodeDataType,
277    TraceData: TraceDataType,
278{
279    fn from(value: NodeContext<NodeData, TraceData>) -> Self {
280        let trace = match value.config.trace {
281            true => Some(RefCell::new(Variable::Null)),
282            false => None,
283        };
284
285        Self {
286            id: value.id,
287            name: value.name,
288            input: value.input,
289            nodes: value.nodes,
290            extensions: value.extensions,
291            iteration: value.iteration,
292            config: value.config,
293            trace,
294        }
295    }
296}
297
298impl<T, E> NodeContextExt<T, NodeContextBase> for Result<T, E>
299where
300    E: Into<Box<dyn std::error::Error>>,
301{
302    type Error = E;
303
304    fn with_node_context<Function, NewError>(
305        self,
306        ctx: &NodeContextBase,
307        f: Function,
308    ) -> Result<T, NodeError>
309    where
310        Function: FnOnce(Self::Error) -> NewError,
311        NewError: Into<Box<dyn std::error::Error>>,
312    {
313        self.map_err(|err| ctx.make_error(f(err)))
314    }
315}
316
317impl<T> NodeContextExt<T, NodeContextBase> for Option<T> {
318    type Error = &'static str;
319
320    fn with_node_context<Function, NewError>(
321        self,
322        ctx: &NodeContextBase,
323        f: Function,
324    ) -> Result<T, NodeError>
325    where
326        Function: FnOnce(Self::Error) -> NewError,
327        NewError: Into<Box<dyn std::error::Error>>,
328    {
329        self.ok_or_else(|| ctx.make_error(f("None")))
330    }
331
332    fn node_context_message(self, ctx: &NodeContextBase, message: &str) -> Result<T, NodeError> {
333        self.with_node_context(ctx, |_| message.to_string())
334    }
335}
336
337#[derive(Debug, Serialize, Error)]
338#[serde(rename_all = "camelCase")]
339struct ValidationErrorJson {
340    path: String,
341    message: String,
342}
343
344impl Display for ValidationErrorJson {
345    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
346        write!(f, "{}: {}", self.path, self.message)
347    }
348}
349
350impl<'a> From<ValidationError<'a>> for ValidationErrorJson {
351    fn from(value: ValidationError<'a>) -> Self {
352        ValidationErrorJson {
353            path: value.instance_path().to_string(),
354            message: format!("{}", value),
355        }
356    }
357}
358
359#[derive(Clone)]
360pub struct NodeContextConfig {
361    pub trace: bool,
362    pub nodes_in_context: bool,
363    pub max_depth: u8,
364    pub function_timeout_millis: u64,
365    pub http_auth: bool,
366    pub validation_salt: u64,
367}
368
369impl Default for NodeContextConfig {
370    fn default() -> Self {
371        Self {
372            trace: false,
373            nodes_in_context: ZEN_CONFIG.nodes_in_context.load(Ordering::Relaxed),
374            function_timeout_millis: ZEN_CONFIG.function_timeout_millis.load(Ordering::Relaxed),
375            http_auth: ZEN_CONFIG.http_auth.load(Ordering::Relaxed),
376            max_depth: 5,
377            validation_salt: 0,
378        }
379    }
380}