* Validate input mappings between nodes
( graph: WorkflowGraphDto, compiledDefinition: WorkflowDefinition, actionPorts: Map<string, ActionPortSnapshot>, errors: ValidationError[], _warnings: ValidationError[], )
| 254 | * Validate input mappings between nodes |
| 255 | */ |
| 256 | function validateInputMappings( |
| 257 | graph: WorkflowGraphDto, |
| 258 | compiledDefinition: WorkflowDefinition, |
| 259 | actionPorts: Map<string, ActionPortSnapshot>, |
| 260 | errors: ValidationError[], |
| 261 | _warnings: ValidationError[], |
| 262 | ) { |
| 263 | const nodes = new Map(graph.nodes.map((n) => [n.id, n])); |
| 264 | |
| 265 | for (const action of compiledDefinition.actions) { |
| 266 | const componentInputs = actionPorts.get(action.ref)?.inputs ?? []; |
| 267 | |
| 268 | // Check if all required inputs have mappings or static values |
| 269 | for (const input of componentInputs) { |
| 270 | const hasStaticValue = Object.hasOwn(action.inputOverrides ?? {}, input.id); |
| 271 | const hasMapping = Object.hasOwn(action.inputMappings ?? {}, input.id); |
| 272 | |
| 273 | if (input.required && !hasStaticValue && !hasMapping) { |
| 274 | errors.push({ |
| 275 | node: action.ref, |
| 276 | field: 'inputMappings', |
| 277 | message: `Required input '${input.label}' (${input.id}) has no mapping or static value`, |
| 278 | severity: 'error', |
| 279 | suggestion: |
| 280 | 'Either provide a static value in node configuration or connect an edge to this input', |
| 281 | }); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | // Validate edge mappings point to valid nodes |
| 286 | for (const [_targetHandle, mapping] of Object.entries(action.inputMappings ?? {})) { |
| 287 | const sourceNode = nodes.get(mapping.sourceRef); |
| 288 | if (!sourceNode) { |
| 289 | errors.push({ |
| 290 | node: action.ref, |
| 291 | field: 'inputMappings', |
| 292 | message: `Edge references unknown source node: ${mapping.sourceRef}`, |
| 293 | severity: 'error', |
| 294 | suggestion: 'Check that the source node exists and the edge is properly connected', |
| 295 | }); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | // Check raw edges for multiple inputs to the same port |
| 300 | const edgesToThisNode = graph.edges.filter((e) => e.target === action.ref); |
| 301 | const portsSeen = new Map<string, number>(); |
| 302 | for (const edge of edgesToThisNode) { |
| 303 | const targetHandle = edge.targetHandle ?? edge.sourceHandle; |
| 304 | if (!targetHandle) continue; |
| 305 | |
| 306 | portsSeen.set(targetHandle, (portsSeen.get(targetHandle) ?? 0) + 1); |
| 307 | } |
| 308 | |
| 309 | for (const [portId, count] of portsSeen.entries()) { |
| 310 | if (count > 1 && portId !== 'tools') { |
| 311 | const inputMetadata = actionPorts.get(action.ref)?.inputs.find((i) => i.id === portId); |
| 312 | const portLabel = inputMetadata?.label || portId; |
| 313 |
no test coverage detected