node-red-contrib-influxdb3 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/influxdb3.js +62 -4
- package/package.json +2 -2
- package/test/line-protocol-validation.test.js +83 -0
package/influxdb3.js
CHANGED
|
@@ -141,16 +141,54 @@ module.exports = function(RED) {
|
|
|
141
141
|
* Safely serialize a value for diagnostic logging.
|
|
142
142
|
* Handles circular references and large objects.
|
|
143
143
|
* @param {*} value
|
|
144
|
+
* @param {number} [maxLength=200] - Maximum length before truncation
|
|
144
145
|
* @returns {string}
|
|
145
146
|
*/
|
|
146
|
-
function safeStringify(value) {
|
|
147
|
+
function safeStringify(value, maxLength) {
|
|
148
|
+
maxLength = maxLength || 200;
|
|
147
149
|
try {
|
|
148
|
-
|
|
150
|
+
const str = JSON.stringify(value);
|
|
151
|
+
if (str && str.length > maxLength) {
|
|
152
|
+
return str.substring(0, maxLength) + '...(truncated)';
|
|
153
|
+
}
|
|
154
|
+
return str;
|
|
149
155
|
} catch (e) {
|
|
150
156
|
return `[unserializable: ${e.message}]`;
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Validate that a string looks like InfluxDB line protocol.
|
|
162
|
+
* Returns null if valid, or an error message string if invalid.
|
|
163
|
+
* @param {string} lp - Trimmed line protocol string
|
|
164
|
+
* @returns {string|null}
|
|
165
|
+
*/
|
|
166
|
+
function validateLineProtocol(lp) {
|
|
167
|
+
// Detect JSON-like strings (both valid JSON and JS object notation)
|
|
168
|
+
if (/^\{[\s\S]*}$/.test(lp) || /^\[[\s\S]*]$/.test(lp)) {
|
|
169
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
170
|
+
return (
|
|
171
|
+
'The payload appears to be a JSON/object string, not line protocol. ' +
|
|
172
|
+
'If you are sending JSON, ensure msg.payload is a parsed object (not a string). ' +
|
|
173
|
+
'Use a JSON parse node before this node to convert the string to an object. ' +
|
|
174
|
+
`Received string: ${preview}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Line protocol must have at least: measurement field=value
|
|
179
|
+
// i.e. at least one space and one '=' in the field set
|
|
180
|
+
if (!lp.includes(' ') || !lp.includes('=')) {
|
|
181
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
182
|
+
return (
|
|
183
|
+
'The payload string does not appear to be valid line protocol. ' +
|
|
184
|
+
'Expected format: measurement[,tag=val] field=val[,field=val] [timestamp]. ' +
|
|
185
|
+
`Received: ${preview}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
154
192
|
/**
|
|
155
193
|
* Process a field value and add it to a Point.
|
|
156
194
|
* Returns true if the field was added, false if it was skipped.
|
|
@@ -363,6 +401,12 @@ module.exports = function(RED) {
|
|
|
363
401
|
if (!lineProtocol) {
|
|
364
402
|
throw new Error('Line protocol string is empty');
|
|
365
403
|
}
|
|
404
|
+
|
|
405
|
+
// Validate line protocol format
|
|
406
|
+
const validationError = validateLineProtocol(lineProtocol);
|
|
407
|
+
if (validationError) {
|
|
408
|
+
throw new Error(validationError);
|
|
409
|
+
}
|
|
366
410
|
} else if (msg.payload && typeof msg.payload === 'object' && !Array.isArray(msg.payload)) {
|
|
367
411
|
const result = buildLineProtocol(msg);
|
|
368
412
|
if (result.error) {
|
|
@@ -370,7 +414,18 @@ module.exports = function(RED) {
|
|
|
370
414
|
}
|
|
371
415
|
lineProtocol = result.lineProtocol;
|
|
372
416
|
} else {
|
|
373
|
-
|
|
417
|
+
const actualType = typeof msg.payload;
|
|
418
|
+
const detail = msg.payload === null
|
|
419
|
+
? 'null'
|
|
420
|
+
: msg.payload === undefined
|
|
421
|
+
? 'undefined'
|
|
422
|
+
: Array.isArray(msg.payload)
|
|
423
|
+
? `Array (length ${msg.payload.length}): ${safeStringify(msg.payload)}`
|
|
424
|
+
: `${actualType}${msg.payload && msg.payload.constructor ? ` [${msg.payload.constructor.name}]` : ''}: ${safeStringify(msg.payload)}`;
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Invalid payload format. Expected string (line protocol) or object with fields. ` +
|
|
427
|
+
`Received: ${detail}`
|
|
428
|
+
);
|
|
374
429
|
}
|
|
375
430
|
|
|
376
431
|
// Write to InfluxDB
|
|
@@ -382,7 +437,10 @@ module.exports = function(RED) {
|
|
|
382
437
|
done();
|
|
383
438
|
|
|
384
439
|
} catch (error) {
|
|
385
|
-
|
|
440
|
+
const shortMsg = error.message
|
|
441
|
+
? (error.message.length > 80 ? error.message.substring(0, 80) + '...' : error.message)
|
|
442
|
+
: 'unknown error';
|
|
443
|
+
setStatus({ fill: 'red', shape: 'dot', text: shortMsg });
|
|
386
444
|
done(error);
|
|
387
445
|
}
|
|
388
446
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-influxdb3",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Node-RED nodes for InfluxDB v3 integration",
|
|
5
5
|
"main": "influxdb3.js",
|
|
6
6
|
"scripts": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"author": "Stuart B",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
19
|
+
"node": ">=18.0.0"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for line protocol string validation.
|
|
3
|
+
* Mirrors the validateLineProtocol logic in influxdb3.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Re-implement the validation logic here to test it in isolation
|
|
7
|
+
// (the function is not exported from influxdb3.js)
|
|
8
|
+
function validateLineProtocol(lp) {
|
|
9
|
+
if (/^\{[\s\S]*}$/.test(lp) || /^\[[\s\S]*]$/.test(lp)) {
|
|
10
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
11
|
+
return (
|
|
12
|
+
'The payload appears to be a JSON/object string, not line protocol. ' +
|
|
13
|
+
'If you are sending JSON, ensure msg.payload is a parsed object (not a string). ' +
|
|
14
|
+
'Use a JSON parse node before this node to convert the string to an object. ' +
|
|
15
|
+
`Received string: ${preview}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!lp.includes(' ') || !lp.includes('=')) {
|
|
20
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
21
|
+
return (
|
|
22
|
+
'The payload string does not appear to be valid line protocol. ' +
|
|
23
|
+
'Expected format: measurement[,tag=val] field=val[,field=val] [timestamp]. ' +
|
|
24
|
+
`Received: ${preview}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('Line protocol string validation', () => {
|
|
32
|
+
test('valid line protocol returns null', () => {
|
|
33
|
+
expect(validateLineProtocol('weather,location=us-midwest temperature=82 1465839830100400200')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('valid line protocol without timestamp returns null', () => {
|
|
37
|
+
expect(validateLineProtocol('weather,location=us-midwest temperature=82')).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('valid line protocol without tags returns null', () => {
|
|
41
|
+
expect(validateLineProtocol('weather temperature=82')).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('valid line protocol with multiple fields returns null', () => {
|
|
45
|
+
expect(validateLineProtocol('weather temperature=82,humidity=71')).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('detects JSON object string', () => {
|
|
49
|
+
const input = '{"fields":{"used":12.0},"tags":{"location":"office"}}';
|
|
50
|
+
const result = validateLineProtocol(input);
|
|
51
|
+
expect(result).toContain('JSON/object string');
|
|
52
|
+
expect(result).toContain('not line protocol');
|
|
53
|
+
expect(result).toContain('JSON parse node');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('detects JS object notation string (unquoted keys)', () => {
|
|
57
|
+
const input = '{fields:{used:12.0,path:root},tags:{location:office,node:grafana2}}';
|
|
58
|
+
const result = validateLineProtocol(input);
|
|
59
|
+
expect(result).toContain('JSON/object string');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('detects JSON array string', () => {
|
|
63
|
+
const input = '[{"measurement":"test","fields":{"value":1}}]';
|
|
64
|
+
const result = validateLineProtocol(input);
|
|
65
|
+
expect(result).toContain('JSON/object string');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('detects string with no space (not line protocol)', () => {
|
|
69
|
+
const result = validateLineProtocol('justameasurement');
|
|
70
|
+
expect(result).toContain('does not appear to be valid line protocol');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('detects string with no equals sign (not line protocol)', () => {
|
|
74
|
+
const result = validateLineProtocol('measurement nofields');
|
|
75
|
+
expect(result).toContain('does not appear to be valid line protocol');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('truncates long strings in error message', () => {
|
|
79
|
+
const longJson = '{' + '"a":1,'.repeat(50) + '"b":2}';
|
|
80
|
+
const result = validateLineProtocol(longJson);
|
|
81
|
+
expect(result).toContain('...');
|
|
82
|
+
});
|
|
83
|
+
});
|