Skip to main content

jellyflow_runtime/rules/
template.rs

1use serde_json::Value;
2
3use jellyflow_core::core::{
4    CanvasPoint, Node, NodeId, NodeKindKey, Port, PortCapacity, PortDirection, PortId, PortKey,
5    PortKind,
6};
7use jellyflow_core::types::TypeDesc;
8
9/// Specification for inserting a new node as part of a connection workflow.
10///
11/// The `ports` list defines the desired UI ordering for the inserted node.
12#[derive(Debug, Clone)]
13pub struct InsertNodeSpec {
14    pub node_id: NodeId,
15    pub node: Node,
16    pub ports: Vec<(PortId, Port)>,
17    pub input: PortId,
18    pub output: PortId,
19}
20
21/// A template for a port to be instantiated during an insertion workflow.
22#[derive(Debug, Clone)]
23pub struct PortTemplate {
24    pub key: PortKey,
25    pub dir: PortDirection,
26    pub kind: PortKind,
27    pub capacity: PortCapacity,
28    pub ty: Option<TypeDesc>,
29    pub data: Value,
30}
31
32/// A template for inserting a node between two ports.
33///
34/// This is the preferred UI-to-rules contract: IDs are generated by the rules layer.
35#[derive(Debug, Clone)]
36pub struct InsertNodeTemplate {
37    pub kind: NodeKindKey,
38    pub kind_version: u32,
39    pub collapsed: bool,
40    pub data: Value,
41    pub ports: Vec<PortTemplate>,
42
43    /// Which instantiated port should be connected from the upstream edge segment.
44    pub input: PortKey,
45    /// Which instantiated port should be connected to the downstream edge segment.
46    pub output: PortKey,
47}
48
49impl InsertNodeTemplate {
50    /// Instantiates this template at a given position by allocating fresh IDs.
51    pub fn instantiate(&self, at: CanvasPoint) -> Result<InsertNodeSpec, String> {
52        let node_id = NodeId::new();
53        let ports = InstantiatedTemplatePorts::from_template(self, node_id)?;
54
55        Ok(InsertNodeSpec {
56            node_id,
57            node: self.instantiate_node(at),
58            ports: ports.ports,
59            input: ports.input,
60            output: ports.output,
61        })
62    }
63
64    fn instantiate_node(&self, at: CanvasPoint) -> Node {
65        Node {
66            kind: self.kind.clone(),
67            kind_version: self.kind_version,
68            pos: at,
69            origin: None,
70            selectable: None,
71            focusable: None,
72            draggable: None,
73            connectable: None,
74            deletable: None,
75            parent: None,
76            extent: None,
77            expand_parent: None,
78            size: None,
79            hidden: false,
80            collapsed: self.collapsed,
81            ports: Vec::new(),
82            data: self.data.clone(),
83        }
84    }
85}
86
87struct InstantiatedTemplatePorts {
88    ports: Vec<(PortId, Port)>,
89    input: PortId,
90    output: PortId,
91}
92
93impl InstantiatedTemplatePorts {
94    fn from_template(template: &InsertNodeTemplate, node_id: NodeId) -> Result<Self, String> {
95        let mut ports: Vec<(PortId, Port)> = Vec::new();
96        let mut input: Option<PortId> = None;
97        let mut output: Option<PortId> = None;
98
99        for port_template in &template.ports {
100            let port_id = PortId::new();
101            if port_template.key == template.input {
102                input = Some(port_id);
103            }
104            if port_template.key == template.output {
105                output = Some(port_id);
106            }
107
108            ports.push((port_id, instantiate_port(node_id, port_template)));
109        }
110
111        let input = required_template_port(input, "input", &template.input)?;
112        let output = required_template_port(output, "output", &template.output)?;
113        if input == output {
114            return Err("template input/output ports must be distinct".to_string());
115        }
116
117        Ok(Self {
118            ports,
119            input,
120            output,
121        })
122    }
123}
124
125fn instantiate_port(node_id: NodeId, template: &PortTemplate) -> Port {
126    Port {
127        node: node_id,
128        key: template.key.clone(),
129        dir: template.dir,
130        kind: template.kind,
131        capacity: template.capacity,
132        connectable: None,
133        connectable_start: None,
134        connectable_end: None,
135        ty: template.ty.clone(),
136        data: template.data.clone(),
137    }
138}
139
140fn required_template_port(
141    port: Option<PortId>,
142    role: &str,
143    key: &PortKey,
144) -> Result<PortId, String> {
145    port.ok_or_else(|| format!("template is missing {role} port: {}", key.0))
146}