| 31 | } |
| 32 | |
| 33 | fn connect(&mut self, from_id: String, to_id: String) -> PyResult<()> { |
| 34 | // If the string is a literal UUID (`add_node` return values), parse it first so a node |
| 35 | // *name* that equals another node's UUID cannot hijack resolution. Otherwise resolve by |
| 36 | // human-readable node name. (NodeId::from_string accepts any string via v5 — do not use |
| 37 | // that path for names.) |
| 38 | let from_node_id = if Uuid::parse_str(from_id.trim()).is_ok() { |
| 39 | NodeId::from_string(&from_id).map_err(to_py_runtime_error)? |
| 40 | } else if let Some(id) = self.inner.graph.get_node_id_by_name(&from_id) { |
| 41 | id |
| 42 | } else { |
| 43 | return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!( |
| 44 | "Source node {} not found", |
| 45 | from_id |
| 46 | ))); |
| 47 | }; |
| 48 | |
| 49 | let to_node_id = if Uuid::parse_str(to_id.trim()).is_ok() { |
| 50 | NodeId::from_string(&to_id).map_err(to_py_runtime_error)? |
| 51 | } else if let Some(id) = self.inner.graph.get_node_id_by_name(&to_id) { |
| 52 | id |
| 53 | } else { |
| 54 | return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!( |
| 55 | "Target node {} not found", |
| 56 | to_id |
| 57 | ))); |
| 58 | }; |
| 59 | |
| 60 | self.inner |
| 61 | .connect_nodes(from_node_id, to_node_id, WorkflowEdge::data_flow()) |
| 62 | .map_err(|e| { |
| 63 | let error_msg = e.to_string(); |
| 64 | if error_msg.contains("not found") || error_msg.contains("Target node") { |
| 65 | PyErr::new::<pyo3::exceptions::PyValueError, _>(error_msg) |
| 66 | } else { |
| 67 | to_py_runtime_error(e) |
| 68 | } |
| 69 | })?; |
| 70 | Ok(()) |
| 71 | } |
| 72 | |
| 73 | fn validate(&self) -> PyResult<()> { |
| 74 | self.inner.validate().map_err(to_py_runtime_error)?; |