• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobotWebTools / rclnodejs / 19030223619

03 Nov 2025 09:41AM UTC coverage: 82.737% (+0.7%) from 82.083%
19030223619

Pull #1318

github

web-flow
Merge 14ed2c5f1 into 7c306aa60
Pull Request #1318: feat: add ParameterClient for external parameter access

997 of 1326 branches covered (75.19%)

Branch coverage included in aggregate %.

148 of 179 new or added lines in 3 files covered. (82.68%)

2 existing lines in 1 file now uncovered.

2262 of 2613 relevant lines covered (86.57%)

516.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.96
/lib/parameter.js
1
// Copyright (c) 2020 Wayne Parrott. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
// Note: parameter api and function based on
16
// https://design.ros2.org/articles/ros_parameters.html
17
// https://github.com/ros2/rcl and
18
// https://github.com/ros2/rclpy
19

20
'use strict';
21

22
const { isClose } = require('./utils.js');
26✔
23

24
/**
25
 * The plus/minus tolerance for determining number equivalence.
26
 * @constant {number}
27
 *
28
 *  @see [FloatingPointRange]{@link FloatingPointRange}
29
 *  @see [IntegerRange]{@link IntegerRange}
30
 */
31
const DEFAULT_NUMERIC_RANGE_TOLERANCE = 1e-6;
26✔
32

33
const PARAMETER_SEPARATOR = '.';
26✔
34
const PARAMETER_BYTE = 10;
26✔
35

36
/**
37
 * Enum for ParameterType
38
 * @readonly
39
 * @enum {number}
40
 */
41
const ParameterType = {
26✔
42
  /** @member {number} */
43
  PARAMETER_NOT_SET: 0,
44
  /** @member {number} */
45
  PARAMETER_BOOL: 1,
46
  /** @member {number} */
47
  PARAMETER_INTEGER: 2,
48
  /** @member {number} */
49
  PARAMETER_DOUBLE: 3,
50
  /** @member {number} */
51
  PARAMETER_STRING: 4,
52
  /** @member {number} */
53
  PARAMETER_BYTE_ARRAY: 5,
54
  /** @member {number} */
55
  PARAMETER_BOOL_ARRAY: 6,
56
  /** @member {number} */
57
  PARAMETER_INTEGER_ARRAY: 7,
58
  /** @member {number} */
59
  PARAMETER_DOUBLE_ARRAY: 8,
60
  /** @member {number} */
61
  PARAMETER_STRING_ARRAY: 9,
62
};
63

64
/**
65
 * A node parameter.
66
 * @class
67
 */
68
class Parameter {
69
  /**
70
   * Create a Parameter instance from an rlc_interfaces/msg/Parameter message.
71
   * @constructs
72
   * @param {rcl_interfaces/msg/Parameter} parameterMsg - The message to convert to a parameter.
73
   * @return {Parameter} - The new instance.
74
   */
75
  static fromParameterMessage(parameterMsg) {
76
    const name = parameterMsg.name;
28✔
77
    const type = parameterMsg.value.type;
28✔
78
    let value;
79

80
    switch (type) {
28✔
81
      case ParameterType.PARAMETER_NOT_SET:
82
        break;
2✔
83
      case ParameterType.PARAMETER_BOOL:
84
        value = parameterMsg.value.bool_value;
4✔
85
        break;
4✔
86
      case ParameterType.PARAMETER_BOOL_ARRAY:
87
        value = parameterMsg.value.bool_array_value;
1✔
88
        break;
1✔
89
      case ParameterType.PARAMETER_BYTE_ARRAY:
90
        value = Array.from(parameterMsg.value.byte_array_value);
3✔
91
        break;
3✔
92
      case ParameterType.PARAMETER_DOUBLE:
93
        value = parameterMsg.value.double_value;
3✔
94
        break;
3✔
95
      case ParameterType.PARAMETER_DOUBLE_ARRAY:
96
        value = Array.from(parameterMsg.value.double_array_value);
1✔
97
        break;
1✔
98
      case ParameterType.PARAMETER_INTEGER:
99
        value = parameterMsg.value.integer_value;
4✔
100
        break;
4✔
101
      case ParameterType.PARAMETER_INTEGER_ARRAY:
102
        value = Array.from(parameterMsg.value.integer_array_value);
1✔
103
        break;
1✔
104
      case ParameterType.PARAMETER_STRING:
105
        value = parameterMsg.value.string_value;
8✔
106
        break;
8✔
107
      case ParameterType.PARAMETER_STRING_ARRAY:
108
        value = Array.from(parameterMsg.value.string_array_value);
1✔
109
        break;
1✔
110
    }
111

112
    return new Parameter(name, type, value);
28✔
113
  }
114

115
  /**
116
   * Create new parameter instances.
117
   * @constructor
118
   *
119
   * @param {string} name - The parameter name, must be a valid name.
120
   * @param {ParameterType} type - The type identifier.
121
   * @param {any} value - The parameter value.
122
   */
123
  constructor(name, type, value) {
124
    this._name = name;
1,926✔
125
    this._type = type;
1,926✔
126
    // Convert to bigint if it's type of `PARAMETER_INTEGER`.
127
    this._value =
1,926✔
128
      this._type == ParameterType.PARAMETER_INTEGER ? BigInt(value) : value;
1,926✔
129
    this._isDirty = true;
1,926✔
130

131
    this.validate();
1,926✔
132
  }
133

134
  /**
135
   * Get name
136
   *
137
   * @return {string} - The parameter name.
138
   */
139
  get name() {
140
    return this._name;
33,850✔
141
  }
142

143
  /**
144
   * Get type
145
   *
146
   * @return {ParameterType} - The parameter type.
147
   */
148
  get type() {
149
    return this._type;
22,812✔
150
  }
151

152
  /**
153
   * Get value.
154
   *
155
   * @return {any} - The parameter value.
156
   */
157
  get value() {
158
    return this._value;
9,004✔
159
  }
160

161
  /**
162
   * Set value.
163
   * Value must be compatible with the type property.
164
   * @param {any} newValue - The parameter name.
165
   */
166
  set value(newValue) {
167
    // no empty array value allowed
168
    this._value =
8✔
169
      Array.isArray(newValue) && newValue.length === 0 ? null : newValue;
16!
170

171
    this._dirty = true;
8✔
172
    this.validate();
8✔
173
  }
174

175
  /**
176
   * Check the state of this property.
177
   * Throw TypeError on first property with invalid type.
178
   * @return {undefined}
179
   */
180
  validate() {
181
    // validate name
182
    if (
5,669!
183
      !this.name ||
17,007✔
184
      typeof this.name !== 'string' ||
185
      this.name.trim().length === 0
186
    ) {
187
      throw new TypeError('Invalid name');
×
188
    }
189

190
    // validate type
191
    if (!validType(this.type)) {
5,669!
192
      throw new TypeError('Invalid type');
×
193
    }
194

195
    // validate value
196
    if (!validValue(this.value, this.type)) {
5,669✔
197
      throw new TypeError('Incompatible value.');
1✔
198
    }
199

200
    this._dirty = false;
5,668✔
201
  }
202

203
  /**
204
   * Create a rcl_interfaces.msg.Parameter from this instance.
205
   *
206
   * @return {rcl_interfaces.msg.Parameter} - The new instance.
207
   */
208
  toParameterMessage() {
209
    const msg = {
1,860✔
210
      name: this.name,
211
      value: this.toParameterValueMessage(),
212
    };
213
    return msg;
1,860✔
214
  }
215

216
  /**
217
   * Create a rcl_interfaces.msg.ParameterValue from this instance.
218
   *
219
   * @return {rcl_interfaces.msg.ParameterValue} - The new instance.
220
   */
221
  toParameterValueMessage() {
222
    const msg = {};
1,889✔
223
    msg.type = this.type;
1,889✔
224
    switch (this.type) {
1,889!
225
      case ParameterType.PARAMETER_NOT_SET:
226
        break;
3✔
227
      case ParameterType.PARAMETER_BOOL:
228
        msg.bool_value = this.value;
1,435✔
229
        break;
1,435✔
230
      case ParameterType.PARAMETER_BOOL_ARRAY:
231
        msg.bool_array_value = this.value;
×
232
        break;
×
233
      case ParameterType.PARAMETER_BYTE_ARRAY:
234
        msg.byte_array_value = this.value.map((val) => Math.trunc(val));
159✔
235
        break;
53✔
236
      case ParameterType.PARAMETER_DOUBLE:
237
        msg.double_value = this.value;
53✔
238
        break;
53✔
239
      case ParameterType.PARAMETER_DOUBLE_ARRAY:
240
        msg.double_array_value = this.value;
49✔
241
        break;
49✔
242
      case ParameterType.PARAMETER_INTEGER:
243
        msg.integer_value = this.value;
65✔
244
        break;
65✔
245
      case ParameterType.PARAMETER_INTEGER_ARRAY:
246
        msg.integer_array_value = this.value;
58✔
247
        break;
58✔
248
      case ParameterType.PARAMETER_STRING:
249
        msg.string_value = this.value;
124✔
250
        break;
124✔
251
      case ParameterType.PARAMETER_STRING_ARRAY:
252
        msg.string_array_value = this.value;
49✔
253
        break;
49✔
254
    }
255

256
    return msg;
1,889✔
257
  }
258
}
259

260
/**
261
 * A node parameter descriptor.
262
 * @class
263
 */
264
class ParameterDescriptor {
265
  /**
266
   * Create a new instance from a parameter.
267
   * @constructs
268
   * @param {Parameter} parameter - The parameter from which new instance is constructed.
269
   * @return {ParameterDescriptor} - The new instance.
270
   */
271
  static fromParameter(parameter) {
272
    const name = parameter.name;
699✔
273
    const type = parameter.type;
699✔
274
    return new ParameterDescriptor(name, type, 'Created from parameter.');
699✔
275
  }
276

277
  /**
278
   * Create new instances.
279
   * @constructor
280
   * @param {string} name - The descriptor name, must be a valid name.
281
   * @param {ParameterType} type - The type identifier.
282
   * @param {string} [description] - A descriptive string.
283
   * @param {boolean} [readOnly] - True indicates a parameter of this type can not be modified. Default = false.
284
   * @param {Range} [range] - An optional IntegerRange or FloatingPointRange.
285
   */
286
  constructor(
287
    name,
288
    type = ParameterType.PARAMETER_NOT_SET,
×
289
    description = 'no description',
413✔
290
    readOnly = false,
1,151✔
291
    range = null
1,864✔
292
  ) {
293
    this._name = name; // string
1,864✔
294
    this._type = type; // ParameterType
1,864✔
295
    this._description = description;
1,864✔
296
    this._readOnly = readOnly;
1,864✔
297
    this._additionalConstraints = '';
1,864✔
298
    this._range = range;
1,864✔
299

300
    this.validate();
1,864✔
301
  }
302

303
  /**
304
   * Get name.
305
   *
306
   * @return {string} - The name property.
307
   */
308
  get name() {
309
    return this._name;
20,431✔
310
  }
311

312
  /**
313
   * Get type.
314
   *
315
   * @return {ParameterType} - The type property.
316
   */
317
  get type() {
318
    return this._type;
7,461✔
319
  }
320

321
  /**
322
   * Get description.
323
   *
324
   * @return {string} - A descriptive string property.
325
   */
326
  get description() {
327
    return this._description;
11,151✔
328
  }
329

330
  /**
331
   * Get readOnly property.
332
   *
333
   * @return {boolean} - The readOnly property.
334
   */
335
  get readOnly() {
336
    return this._readOnly;
25✔
337
  }
338

339
  /**
340
   * Get additionalConstraints property.
341
   *
342
   * @return {string} - The additionalConstraints property.
343
   */
344
  get additionalConstraints() {
345
    return this._additionalConstraints;
5✔
346
  }
347

348
  /**
349
   * Set additionalConstraints property. .
350
   *
351
   * @param {string} constraintDescription - The new value.
352
   */
353
  set additionalConstraints(constraintDescription) {
354
    this._additionalConstraints = constraintDescription;
×
355
  }
356

357
  /**
358
   * Determine if rangeConstraint property has been set.
359
   *
360
   * @return {boolean} - The rangeConstraint property.
361
   */
362
  hasRange() {
363
    return !!this._range;
7,438✔
364
  }
365

366
  /**
367
   * Get range.
368
   *
369
   * @return {FloatingPointRange|IntegerRange} - The range property.
370
   */
371
  get range() {
372
    return this._range;
16✔
373
  }
374

375
  /**
376
   * Set range.
377
   * The range must be compatible with the type property.
378
   * @param {FloatingPointRange|IntegerRange} range - The new range.
379
   */
380
  set range(range) {
381
    if (!range) {
2!
382
      this._range = null;
×
383
      return;
×
384
    }
385
    if (!(range instanceof Range)) {
2!
386
      throw TypeError('Expected instance of Range.');
×
387
    }
388
    if (!range.isValidType(this.type)) {
2!
389
      throw TypeError('Incompatible Range');
×
390
    }
391

392
    this._range = range;
2✔
393
  }
394

395
  /**
396
   * Check the state and ensure it is valid.
397
   * Throw a TypeError if invalid state is detected.
398
   *
399
   * @return {undefined}
400
   */
401
  validate() {
402
    // validate name
403
    if (
5,572!
404
      !this.name ||
16,716✔
405
      typeof this.name !== 'string' ||
406
      this.name.trim().length === 0
407
    ) {
408
      throw new TypeError('Invalid name');
×
409
    }
410

411
    // validate type
412
    if (!validType(this.type)) {
5,572!
413
      throw new TypeError('Invalid type');
×
414
    }
415

416
    // validate description
417
    if (this.description && typeof this.description !== 'string') {
5,572!
418
      throw new TypeError('Invalid description');
×
419
    }
420

421
    // validate rangeConstraint
422
    if (this.hasRange() && !this.range.isValidType(this.type)) {
5,572!
423
      throw new TypeError('Incompatible Range');
×
424
    }
425
  }
426

427
  /**
428
   * Check a parameter for consistency with this descriptor.
429
   * Throw an Error if an inconsistent state is detected.
430
   *
431
   * @param {Parameter} parameter - The parameter to test for consistency.
432
   * @return {undefined}
433
   */
434
  validateParameter(parameter) {
435
    if (!parameter) {
1,866!
436
      throw new TypeError('Parameter is undefined');
×
437
    }
438

439
    // ensure parameter is valid
440
    try {
1,866✔
441
      parameter.validate();
1,866✔
442
    } catch {
443
      throw new TypeError('Parameter is invalid');
×
444
    }
445

446
    // ensure this descriptor is valid
447
    try {
1,866✔
448
      this.validate();
1,866✔
449
    } catch {
450
      throw new Error('Descriptor is invalid.');
×
451
    }
452

453
    if (this.name !== parameter.name) throw new Error('Name mismatch');
1,866!
454
    if (this.type !== parameter.type) throw new Error('Type mismatch');
1,866!
455
    if (this.hasRange() && !this.range.inRange(parameter.value)) {
1,866✔
456
      throw new RangeError('Parameter value is not in descriptor range');
4✔
457
    }
458
  }
459

460
  /**
461
   * Create a rcl_interfaces.msg.ParameterDescriptor from this descriptor.
462
   *
463
   * @return {rcl_interfaces.msg.ParameterDescriptor} - The new message.
464
   */
465
  toMessage() {
466
    const msg = {
5✔
467
      name: this.name,
468
      type: this.type,
469
      description: this.description,
470
      additional_constraints: this.additionalConstraints,
471
      read_only: this.readOnly,
472
    };
473
    if (
5!
474
      (this._type === ParameterType.PARAMETER_INTEGER ||
10✔
475
        this._type === ParameterType.PARAMETER_INTEGER_ARRAY) &&
476
      this._rangeConstraint instanceof IntegerRange
477
    ) {
478
      msg.integer_range = [this._rangeConstraint];
×
479
    } else if (
5!
480
      (this._type === ParameterType.PARAMETER_DOUBLE ||
10!
481
        this._type === ParameterType.PARAMETER_DOUBLE_ARRAY) &&
482
      this._rangeConstraint instanceof FloatingPointRange
483
    ) {
484
      msg.floating_point_range = [this._rangeConstraint];
×
485
    }
486

487
    return msg;
5✔
488
  }
489
}
490

491
/**
492
 * An abstract class defining a range of numbers between 2 points inclusively
493
 * divided by a step value.
494
 * @class
495
 */
496
class Range {
497
  /**
498
   * Create a new instance.
499
   * @constructor
500
   * @param {number} fromValue - The lowest inclusive value in range
501
   * @param {number} toValue - The highest inclusive value in range
502
   * @param {number} step - The internal unit size.
503
   */
504
  constructor(fromValue, toValue, step = 1) {
×
505
    this._fromValue = fromValue;
4✔
506
    this._toValue = toValue;
4✔
507
    this._step = step;
4✔
508
  }
509

510
  /**
511
   * Get fromValue.
512
   *
513
   * @return {number} - The lowest inclusive value in range.
514
   */
515
  get fromValue() {
516
    return this._fromValue;
35✔
517
  }
518

519
  /**
520
   * Get toValue.
521
   *
522
   * @return {number} - The highest inclusive value in range.
523
   */
524
  get toValue() {
525
    return this._toValue;
35✔
526
  }
527

528
  /**
529
   * Get step unit.
530
   *
531
   * @return {number} - The internal unit size.
532
   */
533
  get step() {
534
    return this._step;
31✔
535
  }
536

537
  /**
538
   * Determine if a value is within this range.
539
   * A TypeError is thrown when value is not a number or bigint.
540
   * Subclasses should override and call this method for basic type checking.
541
   *
542
   * @param {number|bigint} value - The number or bigint to check.
543
   * @return {boolean} - True if value satisfies the range; false otherwise.
544
   */
545
  inRange(value) {
546
    if (Array.isArray(value)) {
9!
547
      const valArr = value;
×
548
      return valArr.reduce(
×
549
        (inRange, val) => inRange && this.inRange(val),
×
550
        true
551
      );
552
    } else if (typeof value !== 'number' && typeof value !== 'bigint') {
9!
553
      throw new TypeError('Value must be a number or bigint');
×
554
    }
555

556
    return true;
9✔
557
  }
558

559
  /**
560
   * Abstract method that determines if a ParameterType is compatible.
561
   * Subclasses must implement this method.
562
   *
563
   * @param {ParameterType} parameterType - The parameter type to test.
564
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
565
   */
566
  // eslint-disable-next-line no-unused-vars
567
  isValidType(parameterType) {
568
    return false;
×
569
  }
570
}
571

572
/**
573
 * Defines a range for floating point values.
574
 * @class
575
 */
576
class FloatingPointRange extends Range {
577
  /**
578
   * Create a new instance.
579
   * @constructor
580
   * @param {number} fromValue - The lowest inclusive value in range
581
   * @param {number} toValue - The highest inclusive value in range
582
   * @param {number} step - The internal unit size.
583
   * @param {number} tolerance - The plus/minus tolerance for number equivalence.
584
   */
585
  constructor(
586
    fromValue,
587
    toValue,
588
    step = 1,
×
589
    tolerance = DEFAULT_NUMERIC_RANGE_TOLERANCE
1✔
590
  ) {
591
    super(fromValue, toValue, step);
1✔
592
    this._tolerance = tolerance;
1✔
593
  }
594

595
  get tolerance() {
596
    return this._tolerance;
20✔
597
  }
598

599
  /**
600
   * Determine if a ParameterType is compatible.
601
   *
602
   * @param {ParameterType} parameterType - The parameter type to test.
603
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
604
   */
605
  isValidType(parameterType) {
606
    const result =
607
      parameterType === ParameterType.PARAMETER_DOUBLE ||
×
608
      parameterType === ParameterType.PARAMETER_DOUBLE_ARRAY;
609
    return result;
×
610
  }
611

612
  /**
613
   * Determine if a value is within this range.
614
   * A TypeError is thrown when value is not a number.
615
   *
616
   * @param {number} value - The number to check.
617
   * @return {boolean} - True if value satisfies the range; false otherwise.
618
   */
619
  inRange(value) {
620
    if (!super.inRange(value)) return false;
9!
621

622
    const min = Math.min(this.fromValue, this.toValue);
9✔
623
    const max = Math.max(this.fromValue, this.toValue);
9✔
624

625
    if (
9✔
626
      isClose(value, min, this.tolerance) ||
17✔
627
      isClose(value, max, this.tolerance)
628
    ) {
629
      return true;
4✔
630
    }
631
    if (value < min || value > max) {
5✔
632
      return false;
2✔
633
    }
634
    if (this.step != 0.0) {
3!
635
      const distanceInSteps = Math.round((value - min) / this.step);
3✔
636
      if (!isClose(min + distanceInSteps * this.step, value, this.tolerance)) {
3!
637
        return false;
×
638
      }
639
    }
640

641
    return true;
3✔
642
  }
643
}
644

645
/**
646
 * Defines a range for integer values.
647
 * @class
648
 */
649
class IntegerRange extends Range {
650
  /**
651
   * Create a new instance.
652
   * @constructor
653
   * @param {bigint} fromValue - The lowest inclusive value in range
654
   * @param {bigint} toValue - The highest inclusive value in range
655
   * @param {bigint} step - The internal unit size.
656
   */
657
  constructor(fromValue, toValue, step = 1n) {
1✔
658
    super(fromValue, toValue, step);
3✔
659
  }
660

661
  /**
662
   * Determine if a ParameterType is compatible.
663
   *
664
   * @param {ParameterType} parameterType - The parameter type to test.
665
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
666
   */
667
  isValidType(parameterType) {
668
    const result =
669
      parameterType === ParameterType.PARAMETER_INTEGER ||
10!
670
      parameterType === ParameterType.PARAMETER_INTEGER_ARRAY;
671
    return result;
10✔
672
  }
673

674
  inRange(value) {
675
    const min = this.fromValue;
15✔
676
    const max = this.toValue;
15✔
677
    if (value < min || value > max) {
15✔
678
      return false;
5✔
679
    }
680

681
    if (this.step != 0n && (value - min) % this.step !== 0n) {
10✔
682
      return false;
1✔
683
    }
684
    return true;
9✔
685
  }
686
}
687

688
/**
689
 * Infer a ParameterType for JS primitive types:
690
 * string, boolean, number and arrays of these types.
691
 * A TypeError is thrown for a value who's type is not one of
692
 * the set listed.
693
 * @param {any} value - The value to infer it's ParameterType
694
 * @returns {ParameterType} - The ParameterType that best scribes the value.
695
 */
696
function parameterTypeFromValue(value) {
697
  if (value === null || value === undefined) {
11!
NEW
698
    return ParameterType.PARAMETER_NOT_SET;
×
699
  }
700

701
  if (typeof value === 'boolean') {
11✔
702
    return ParameterType.PARAMETER_BOOL;
2✔
703
  }
704

705
  if (typeof value === 'string') {
9✔
706
    return ParameterType.PARAMETER_STRING;
2✔
707
  }
708

709
  if (typeof value === 'bigint') {
7!
NEW
710
    return ParameterType.PARAMETER_INTEGER;
×
711
  }
712

713
  if (typeof value === 'number') {
7✔
714
    // Distinguish between integer and double
715
    return Number.isInteger(value)
5✔
716
      ? ParameterType.PARAMETER_INTEGER
717
      : ParameterType.PARAMETER_DOUBLE;
718
  }
719

720
  // Handle TypedArrays
721
  if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
2!
722
    if (value instanceof Uint8Array) {
2!
723
      return ParameterType.PARAMETER_BYTE_ARRAY;
2✔
724
    }
725
    // For other typed arrays, infer from first element
726
    if (value.length > 0) {
×
NEW
727
      const firstType = parameterTypeFromValue(value[0]);
×
NEW
728
      if (firstType === ParameterType.PARAMETER_INTEGER) {
×
NEW
729
        return ParameterType.PARAMETER_INTEGER_ARRAY;
×
730
      }
NEW
731
      return ParameterType.PARAMETER_DOUBLE_ARRAY;
×
732
    }
UNCOV
733
    return ParameterType.PARAMETER_NOT_SET;
×
734
  }
735

NEW
736
  if (Array.isArray(value)) {
×
NEW
737
    if (value.length === 0) {
×
NEW
738
      return ParameterType.PARAMETER_NOT_SET;
×
739
    }
740

NEW
741
    const elementType = parameterTypeFromValue(value[0]);
×
NEW
742
    switch (elementType) {
×
743
      case ParameterType.PARAMETER_BOOL:
NEW
744
        return ParameterType.PARAMETER_BOOL_ARRAY;
×
745
      case ParameterType.PARAMETER_INTEGER:
NEW
746
        return ParameterType.PARAMETER_INTEGER_ARRAY;
×
747
      case ParameterType.PARAMETER_DOUBLE:
NEW
748
        return ParameterType.PARAMETER_DOUBLE_ARRAY;
×
749
      case ParameterType.PARAMETER_STRING:
NEW
750
        return ParameterType.PARAMETER_STRING_ARRAY;
×
751
      default:
NEW
752
        return ParameterType.PARAMETER_NOT_SET;
×
753
    }
754
  }
755

756
  // Unrecognized value type
UNCOV
757
  throw new TypeError('Unrecognized parameter type.');
×
758
}
759

760
/**
761
 * Determine if a number maps to is a valid ParameterType.
762
 *
763
 * @param {ParameterType} parameterType - The value to test.
764
 * @return {boolean} - True if value is a valid ParameterType; false otherwise.
765
 */
766
function validType(parameterType) {
767
  let result =
768
    typeof parameterType === 'number' &&
11,241✔
769
    ParameterType.PARAMETER_NOT_SET <=
770
      parameterType <=
771
      ParameterType.PARAMETER_STRING_ARRAY;
772

773
  return result;
11,241✔
774
}
775

776
/**
777
 * Test if value can be represented by a ParameterType.
778
 *
779
 * @param {number} value - The value to test.
780
 * @param {ParameterType} type - The ParameterType to test value against.
781
 * @return {boolean} - True if value can be represented by type.
782
 */
783
function validValue(value, type) {
784
  if (value == null) {
7,390✔
785
    return type === ParameterType.PARAMETER_NOT_SET;
6✔
786
  }
787

788
  let result = true;
7,384✔
789
  switch (type) {
7,384!
790
    case ParameterType.PARAMETER_NOT_SET:
791
      result = !value;
×
792
      break;
×
793
    case ParameterType.PARAMETER_BOOL:
794
      result = typeof value === 'boolean';
4,311✔
795
      break;
4,311✔
796
    case ParameterType.PARAMETER_STRING:
797
      result = typeof value === 'string';
663✔
798
      break;
663✔
799
    case ParameterType.PARAMETER_DOUBLE:
800
    case PARAMETER_BYTE:
801
      result = typeof value === 'number';
1,057✔
802
      break;
1,057✔
803
    case ParameterType.PARAMETER_INTEGER:
804
      result = typeof value === 'bigint';
732✔
805
      break;
732✔
806
    case ParameterType.PARAMETER_BOOL_ARRAY:
807
    case ParameterType.PARAMETER_BYTE_ARRAY:
808
    case ParameterType.PARAMETER_INTEGER_ARRAY:
809
    case ParameterType.PARAMETER_DOUBLE_ARRAY:
810
    case ParameterType.PARAMETER_STRING_ARRAY:
811
      result = _validArray(value, type);
621✔
812
      break;
621✔
813
    default:
814
      result = false;
×
815
  }
816

817
  return result;
7,384✔
818
}
819

820
function _validArray(values, type) {
821
  if (!Array.isArray(values)) return false;
621!
822

823
  let arrayElementType;
824
  if (type === ParameterType.PARAMETER_BOOL_ARRAY) {
621✔
825
    arrayElementType = ParameterType.PARAMETER_BOOL;
2✔
826
  } else if (type === ParameterType.PARAMETER_BYTE_ARRAY) {
619✔
827
    arrayElementType = PARAMETER_BYTE;
154✔
828
  }
829
  if (type === ParameterType.PARAMETER_INTEGER_ARRAY) {
621✔
830
    arrayElementType = ParameterType.PARAMETER_INTEGER;
173✔
831
  }
832
  if (type === ParameterType.PARAMETER_DOUBLE_ARRAY) {
621✔
833
    arrayElementType = ParameterType.PARAMETER_DOUBLE;
146✔
834
  }
835
  if (type === ParameterType.PARAMETER_STRING_ARRAY) {
621✔
836
    arrayElementType = ParameterType.PARAMETER_STRING;
146✔
837
  }
838

839
  return values.reduce(
621✔
840
    (compatible, val) =>
841
      compatible &&
1,721✔
842
      !_isArrayParameterType(arrayElementType) &&
843
      validValue(val, arrayElementType),
844
    true
845
  );
846
}
847

848
function _isArrayParameterType(type) {
849
  return (
1,721✔
850
    type === ParameterType.PARAMETER_BOOL_ARRAY ||
8,605✔
851
    type === ParameterType.PARAMETER_BYTE_ARRAY ||
852
    type === ParameterType.PARAMETER_INTEGER_ARRAY ||
853
    type === ParameterType.PARAMETER_DOUBLE_ARRAY ||
854
    type === ParameterType.PARAMETER_STRING_ARRAY
855
  );
856
}
857

858
module.exports = {
26✔
859
  ParameterType,
860
  Parameter,
861
  ParameterDescriptor,
862
  PARAMETER_SEPARATOR,
863
  Range,
864
  FloatingPointRange,
865
  IntegerRange,
866
  DEFAULT_NUMERIC_RANGE_TOLERANCE,
867
  parameterTypeFromValue,
868
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc