Skip to main content

jellyflow_core/core/model/
binding.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::core::ids::{EdgeId, GroupId, NodeId, PortId, StickyNoteId};
5
6/// Persisted relationship between graph-local content and a knowledge source anchor.
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct Binding {
9    /// Stable local endpoint inside this graph.
10    pub subject: BindingEndpoint,
11    /// Stable target endpoint, usually an opaque host-owned source anchor.
12    pub target: BindingEndpoint,
13    /// Optional domain-specific relationship label.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub kind: Option<String>,
16    /// Arbitrary domain metadata.
17    #[serde(default, skip_serializing_if = "Value::is_null")]
18    pub meta: Value,
19}
20
21/// One side of a binding relationship.
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(tag = "kind", rename_all = "snake_case")]
24pub enum BindingEndpoint {
25    /// A graph-local object that Jellyflow can validate structurally.
26    GraphLocal { target: GraphLocalBindingTarget },
27    /// A host-owned source anchor that Jellyflow stores opaquely.
28    Source { anchor: SourceAnchor },
29}
30
31/// A graph-local binding target.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
33#[serde(tag = "kind", rename_all = "snake_case")]
34pub enum GraphLocalBindingTarget {
35    Graph,
36    Node { id: NodeId },
37    Port { id: PortId },
38    Edge { id: EdgeId },
39    Group { id: GroupId },
40    StickyNote { id: StickyNoteId },
41}
42
43/// Opaque host-owned source anchor.
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct SourceAnchor {
46    /// Stable source document/resource identifier owned by the host product.
47    pub source_id: String,
48    /// Host-defined anchor payload, such as PDF coordinates or text ranges.
49    #[serde(default)]
50    pub payload: Value,
51}
52
53impl Binding {
54    pub fn new(subject: BindingEndpoint, target: BindingEndpoint) -> Self {
55        Self {
56            subject,
57            target,
58            kind: None,
59            meta: Value::Null,
60        }
61    }
62
63    pub fn graph_local_to_source(
64        subject: GraphLocalBindingTarget,
65        source_id: impl Into<String>,
66        payload: Value,
67    ) -> Self {
68        Self::new(
69            BindingEndpoint::graph_local(subject),
70            BindingEndpoint::source_payload(source_id, payload),
71        )
72    }
73
74    pub fn node_to_source(node: NodeId, source_id: impl Into<String>, payload: Value) -> Self {
75        Self::graph_local_to_source(GraphLocalBindingTarget::node(node), source_id, payload)
76    }
77
78    pub fn with_kind(mut self, kind: impl Into<String>) -> Self {
79        self.kind = Some(kind.into());
80        self
81    }
82
83    pub fn with_meta(mut self, meta: Value) -> Self {
84        self.meta = meta;
85        self
86    }
87}
88
89impl BindingEndpoint {
90    pub fn graph_local(target: GraphLocalBindingTarget) -> Self {
91        Self::GraphLocal { target }
92    }
93
94    pub fn graph() -> Self {
95        Self::graph_local(GraphLocalBindingTarget::Graph)
96    }
97
98    pub fn node(id: NodeId) -> Self {
99        Self::graph_local(GraphLocalBindingTarget::node(id))
100    }
101
102    pub fn port(id: PortId) -> Self {
103        Self::graph_local(GraphLocalBindingTarget::port(id))
104    }
105
106    pub fn edge(id: EdgeId) -> Self {
107        Self::graph_local(GraphLocalBindingTarget::edge(id))
108    }
109
110    pub fn group(id: GroupId) -> Self {
111        Self::graph_local(GraphLocalBindingTarget::group(id))
112    }
113
114    pub fn sticky_note(id: StickyNoteId) -> Self {
115        Self::graph_local(GraphLocalBindingTarget::sticky_note(id))
116    }
117
118    pub fn source(anchor: SourceAnchor) -> Self {
119        Self::Source { anchor }
120    }
121
122    pub fn source_payload(source_id: impl Into<String>, payload: Value) -> Self {
123        Self::source(SourceAnchor::new(source_id, payload))
124    }
125
126    pub fn graph_local_target(&self) -> Option<GraphLocalBindingTarget> {
127        match self {
128            Self::GraphLocal { target } => Some(*target),
129            Self::Source { .. } => None,
130        }
131    }
132}
133
134impl GraphLocalBindingTarget {
135    pub fn node(id: NodeId) -> Self {
136        Self::Node { id }
137    }
138
139    pub fn port(id: PortId) -> Self {
140        Self::Port { id }
141    }
142
143    pub fn edge(id: EdgeId) -> Self {
144        Self::Edge { id }
145    }
146
147    pub fn group(id: GroupId) -> Self {
148        Self::Group { id }
149    }
150
151    pub fn sticky_note(id: StickyNoteId) -> Self {
152        Self::StickyNote { id }
153    }
154}
155
156impl SourceAnchor {
157    pub fn new(source_id: impl Into<String>, payload: Value) -> Self {
158        Self {
159            source_id: source_id.into(),
160            payload,
161        }
162    }
163}