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