(node: FlowNode, availableNodes?: NodeDataSchema[])
| 173 | * nested config schemas via `availableNodes.find(n => n.name === componentName)`. |
| 174 | */ |
| 175 | export function validateNode(node: FlowNode, availableNodes?: NodeDataSchema[]): ValidationError[] { |
| 176 | const errors: ValidationError[] = [] |
| 177 | |
| 178 | // Check required fields |
| 179 | if (!node.data.name) { |
| 180 | errors.push({ |
| 181 | nodeId: node.id, |
| 182 | message: 'Node is missing a name', |
| 183 | type: 'error' |
| 184 | }) |
| 185 | } |
| 186 | |
| 187 | const schemaFromAvailable = availableNodes?.find((n) => n.name === node.data.name) |
| 188 | const inputParams = schemaFromAvailable?.inputs || node.data.inputParams || [] |
| 189 | const inputValues = node.data.inputs || {} |
| 190 | |
| 191 | for (const param of inputParams) { |
| 192 | // Credential validation (skip general check to avoid duplicate errors) |
| 193 | if (param.name === 'credential') { |
| 194 | if (!param.optional && !inputValues[param.name]) { |
| 195 | errors.push({ |
| 196 | nodeId: node.id, |
| 197 | message: 'Credential is required', |
| 198 | type: 'warning' |
| 199 | }) |
| 200 | } |
| 201 | continue |
| 202 | } |
| 203 | |
| 204 | // Check required inputs, skipping hidden params. |
| 205 | // asyncOptions and asyncMultiOptions values are stored in inputValues just like options; |
| 206 | // evaluateParamVisibility correctly uses those values to resolve show/hide conditions on |
| 207 | // dependent fields, so async-driven visibility is handled automatically here. |
| 208 | if (!param.optional && evaluateParamVisibility(param, inputValues) && isEmptyValue(inputValues[param.name] ?? param.default)) { |
| 209 | errors.push({ |
| 210 | nodeId: node.id, |
| 211 | message: `${param.label || param.name} is required`, |
| 212 | type: 'warning' |
| 213 | }) |
| 214 | } |
| 215 | |
| 216 | // Array item sub-field validation |
| 217 | if (param.type === 'array' && Array.isArray(inputValues[param.name]) && param.array) { |
| 218 | const arrayItems = inputValues[param.name] as Record<string, unknown>[] |
| 219 | |
| 220 | if (arrayItems.length > 0) { |
| 221 | arrayItems.forEach((item, index) => { |
| 222 | for (const arrayParam of param.array!) { |
| 223 | // Evaluate visibility with array index for $index-based conditions |
| 224 | const shouldValidate = evaluateParamVisibility(arrayParam, item as Record<string, unknown>, index) |
| 225 | |
| 226 | if (shouldValidate && !arrayParam.optional) { |
| 227 | const value = item[arrayParam.name] |
| 228 | if (isEmptyValue(value)) { |
| 229 | errors.push({ |
| 230 | nodeId: node.id, |
| 231 | message: `${param.label} item #${index + 1}: ${arrayParam.label} is required`, |
| 232 | type: 'warning' |
no test coverage detected