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 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
- return JSON.stringify(value);
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
- throw new Error('Invalid payload format. Expected string (line protocol) or object with fields');
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
- setStatus({ fill: 'red', shape: 'dot', text: 'error' });
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.4",
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": ">=14.0.0"
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
+ });