Просмотр исходного кода

fix: Set Fields throws instead of silently dropping data on bad input

keep-all mode now rejects array/scalar sources instead of silently
turning them into an empty object, and setByPath now throws instead
of clobbering a non-object value (including arrays) it passes through
on the way to a nested path. Both were silent data loss; the project
rule is nodes throw on failure.

Adds three error fixtures since the runner engine's execution loop
breaks on the first node failure (continueOnError is not exposed by
verify-node.py), so three independent failure scenarios cannot be
asserted from a single workflow execution.
fszontagh 1 месяц назад
Родитель
Сommit
af7ccedd1c

+ 12 - 4
nodes/core/set-fields.js

@@ -64,8 +64,13 @@ function setByPath(target, path, value) {
     let cursor = target;
     for (let i = 0; i < keys.length - 1; i++) {
         const key = keys[i];
-        if (cursor[key] === null || typeof cursor[key] !== 'object') {
+        const existing = cursor[key];
+        if (existing === undefined) {
             cursor[key] = {};
+        } else if (existing === null || typeof existing !== 'object' || Array.isArray(existing)) {
+            const held = Array.isArray(existing) ? 'an array' : existing === null ? 'null' : 'a ' + typeof existing;
+            throw new Error('Cannot set "' + path + '": "' + keys.slice(0, i + 1).join('.') +
+                '" already holds ' + held + ', which would be overwritten');
         }
         cursor = cursor[key];
     }
@@ -98,9 +103,12 @@ async function execute(config, input, context) {
     if (mode === 'only-set') {
         output = {};
     } else {
-        output = source !== null && typeof source === 'object' && !Array.isArray(source)
-            ? JSON.parse(JSON.stringify(source))
-            : {};
+        if (source === null || typeof source !== 'object' || Array.isArray(source)) {
+            const got = Array.isArray(source) ? 'an array' : source === null ? 'null' : 'a ' + typeof source;
+            throw new Error('Set Fields: keep-all needs an object input, got ' + got +
+                '. Use only-set mode, or Split Out first.');
+        }
+        output = JSON.parse(JSON.stringify(source));
     }
 
     for (const field of fields) {

+ 22 - 0
tests/nodes/set-fields-errors-scalar-path.json

@@ -0,0 +1,22 @@
+{
+  "name": "verify-set-fields-errors-scalar-path",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "ScalarPathFixture", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "return { user: 'ada' };"}},
+    {"id": "n3", "name": "SetOnScalarPath", "type": "set-fields", "position": {"x": 0, "y": 200},
+     "config": {
+       "mode": "keep-all",
+       "fields": [
+         {"name": "user.city", "value": "Budapest"}
+       ]
+     }}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "result", "targetNodeId": "n3", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "failed"}
+  }
+}

+ 22 - 0
tests/nodes/set-fields-errors-string.json

@@ -0,0 +1,22 @@
+{
+  "name": "verify-set-fields-errors-string",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "StringFixture", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "return 'just a string';"}},
+    {"id": "n3", "name": "SetOnString", "type": "set-fields", "position": {"x": 0, "y": 200},
+     "config": {
+       "mode": "keep-all",
+       "fields": [
+         {"name": "label", "value": "hello"}
+       ]
+     }}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "result", "targetNodeId": "n3", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "failed"}
+  }
+}

+ 22 - 0
tests/nodes/set-fields-errors.json

@@ -0,0 +1,22 @@
+{
+  "name": "verify-set-fields-errors-array",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "ArrayFixture", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "return [1, 2, 3];"}},
+    {"id": "n3", "name": "SetOnArray", "type": "set-fields", "position": {"x": 0, "y": 200},
+     "config": {
+       "mode": "keep-all",
+       "fields": [
+         {"name": "label", "value": "hello"}
+       ]
+     }}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"},
+    {"sourceNodeId": "n2", "sourceOutput": "result", "targetNodeId": "n3", "targetInput": "data"}
+  ],
+  "expect": {
+    "n3": {"status": "failed"}
+  }
+}