1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use crate::handler::node::{NodeRequest, NodeResult};
use crate::model::{DecisionNode, DecisionNodeKind};
use anyhow::anyhow;
use json_dotpath::DotPaths;
use serde::Serialize;
use serde_json::Value;
use zen_tmpl::TemplateRenderError;

pub trait CustomNodeAdapter {
    fn handle(
        &self,
        request: CustomNodeRequest<'_>,
    ) -> impl std::future::Future<Output = NodeResult> + Send;
}

#[derive(Default, Debug)]
pub struct NoopCustomNode;

impl CustomNodeAdapter for NoopCustomNode {
    async fn handle(&self, _: CustomNodeRequest<'_>) -> NodeResult {
        Err(anyhow!("Custom node handler not provided"))
    }
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomNodeRequest<'a> {
    pub input: &'a Value,
    pub node: CustomDecisionNode<'a>,
}

impl<'a> TryFrom<&'a NodeRequest<'a>> for CustomNodeRequest<'a> {
    type Error = ();

    fn try_from(value: &'a NodeRequest<'a>) -> Result<Self, Self::Error> {
        Ok(Self {
            input: &value.input,
            node: value.node.try_into()?,
        })
    }
}

impl<'a> CustomNodeRequest<'a> {
    pub fn get_field(&self, path: &str) -> Result<Option<Value>, TemplateRenderError> {
        let Some(selected_value) = self.get_field_raw(path) else {
            return Ok(None);
        };

        let Value::String(template) = selected_value else {
            return Ok(Some(selected_value));
        };

        let template_value = zen_tmpl::render(template.as_str(), &self.input)?;
        Ok(Some(template_value))
    }

    fn get_field_raw(&self, path: &str) -> Option<Value> {
        self.node.config.dot_get(path).ok().flatten()
    }
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomDecisionNode<'a> {
    pub id: &'a str,
    pub name: &'a str,
    pub kind: &'a str,
    pub config: &'a Value,
}

impl<'a> TryFrom<&'a DecisionNode> for CustomDecisionNode<'a> {
    type Error = ();

    fn try_from(value: &'a DecisionNode) -> Result<Self, Self::Error> {
        let DecisionNodeKind::CustomNode { content } = &value.kind else {
            return Err(());
        };

        Ok(Self {
            id: &value.id,
            name: &value.name,
            kind: &content.kind,
            config: &content.config,
        })
    }
}