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

RobotWebTools / rclnodejs / 19690312781

26 Nov 2025 02:15AM UTC coverage: 82.843% (+1.1%) from 81.767%
19690312781

push

github

minggangw
Pump to 1.7.0 (#1329)

1074 of 1420 branches covered (75.63%)

Branch coverage included in aggregate %.

2446 of 2829 relevant lines covered (86.46%)

488.08 hits per line

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

75.66
/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
const {
24
  TypeValidationError,
25
  RangeValidationError,
26
  ParameterError,
27
  ParameterTypeError,
28
} = require('./errors.js');
26✔
29

30
/**
31
 * The plus/minus tolerance for determining number equivalence.
32
 * @constant {number}
33
 *
34
 *  @see [FloatingPointRange]{@link FloatingPointRange}
35
 *  @see [IntegerRange]{@link IntegerRange}
36
 */
37
const DEFAULT_NUMERIC_RANGE_TOLERANCE = 1e-6;
26✔
38

39
const PARAMETER_SEPARATOR = '.';
26✔
40
const PARAMETER_BYTE = 10;
26✔
41

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

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

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

118
    return new Parameter(name, type, value);
28✔
119
  }
120

121
  /**
122
   * Create new parameter instances.
123
   * @constructor
124
   *
125
   * @param {string} name - The parameter name, must be a valid name.
126
   * @param {ParameterType} type - The type identifier.
127
   * @param {any} value - The parameter value.
128
   */
129
  constructor(name, type, value) {
130
    this._name = name;
2,225✔
131
    this._type = type;
2,225✔
132
    // Convert to bigint if it's type of `PARAMETER_INTEGER`.
133
    this._value =
2,225✔
134
      this._type == ParameterType.PARAMETER_INTEGER ? BigInt(value) : value;
2,225✔
135
    this._isDirty = true;
2,225✔
136

137
    this.validate();
2,225✔
138
  }
139

140
  /**
141
   * Get name
142
   *
143
   * @return {string} - The parameter name.
144
   */
145
  get name() {
146
    return this._name;
39,277✔
147
  }
148

149
  /**
150
   * Get type
151
   *
152
   * @return {ParameterType} - The parameter type.
153
   */
154
  get type() {
155
    return this._type;
26,444✔
156
  }
157

158
  /**
159
   * Get value.
160
   *
161
   * @return {any} - The parameter value.
162
   */
163
  get value() {
164
    return this._value;
10,366✔
165
  }
166

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

177
    this._dirty = true;
8✔
178
    this.validate();
8✔
179
  }
180

181
  /**
182
   * Check the state of this property.
183
   * Throw TypeError on first property with invalid type.
184
   * @return {undefined}
185
   */
186
  validate() {
187
    // validate name
188
    if (
6,558!
189
      !this.name ||
19,674✔
190
      typeof this.name !== 'string' ||
191
      this.name.trim().length === 0
192
    ) {
193
      throw new TypeValidationError('name', this.name, 'non-empty string', {
×
194
        entityType: 'parameter',
195
      });
196
    }
197

198
    // validate type
199
    if (!validType(this.type)) {
6,558!
200
      throw new ParameterError('Invalid parameter type', this.name, {
×
201
        details: { providedType: this.type },
202
      });
203
    }
204

205
    // validate value
206
    if (!validValue(this.value, this.type)) {
6,558✔
207
      throw new ParameterTypeError(this.name, this.type, typeof this.value, {
1✔
208
        details: {
209
          providedValue: this.value,
210
        },
211
      });
212
    }
213

214
    this._dirty = false;
6,557✔
215
  }
216

217
  /**
218
   * Create a rcl_interfaces.msg.Parameter from this instance.
219
   *
220
   * @return {rcl_interfaces.msg.Parameter} - The new instance.
221
   */
222
  toParameterMessage() {
223
    const msg = {
2,155✔
224
      name: this.name,
225
      value: this.toParameterValueMessage(),
226
    };
227
    return msg;
2,155✔
228
  }
229

230
  /**
231
   * Create a rcl_interfaces.msg.ParameterValue from this instance.
232
   *
233
   * @return {rcl_interfaces.msg.ParameterValue} - The new instance.
234
   */
235
  toParameterValueMessage() {
236
    const msg = {};
2,188✔
237
    msg.type = this.type;
2,188✔
238
    switch (this.type) {
2,188!
239
      case ParameterType.PARAMETER_NOT_SET:
240
        break;
3✔
241
      case ParameterType.PARAMETER_BOOL:
242
        msg.bool_value = this.value;
1,601✔
243
        break;
1,601✔
244
      case ParameterType.PARAMETER_BOOL_ARRAY:
245
        msg.bool_array_value = this.value;
×
246
        break;
×
247
      case ParameterType.PARAMETER_BYTE_ARRAY:
248
        msg.byte_array_value = this.value.map((val) => Math.trunc(val));
159✔
249
        break;
53✔
250
      case ParameterType.PARAMETER_DOUBLE:
251
        msg.double_value = this.value;
96✔
252
        break;
96✔
253
      case ParameterType.PARAMETER_DOUBLE_ARRAY:
254
        msg.double_array_value = this.value;
49✔
255
        break;
49✔
256
      case ParameterType.PARAMETER_INTEGER:
257
        msg.integer_value = this.value;
113✔
258
        break;
113✔
259
      case ParameterType.PARAMETER_INTEGER_ARRAY:
260
        msg.integer_array_value = this.value;
58✔
261
        break;
58✔
262
      case ParameterType.PARAMETER_STRING:
263
        msg.string_value = this.value;
166✔
264
        break;
166✔
265
      case ParameterType.PARAMETER_STRING_ARRAY:
266
        msg.string_array_value = this.value;
49✔
267
        break;
49✔
268
    }
269

270
    return msg;
2,188✔
271
  }
272
}
273

274
/**
275
 * A node parameter descriptor.
276
 * @class
277
 */
278
class ParameterDescriptor {
279
  /**
280
   * Create a new instance from a parameter.
281
   * @constructs
282
   * @param {Parameter} parameter - The parameter from which new instance is constructed.
283
   * @return {ParameterDescriptor} - The new instance.
284
   */
285
  static fromParameter(parameter) {
286
    const name = parameter.name;
903✔
287
    const type = parameter.type;
903✔
288
    return new ParameterDescriptor(name, type, 'Created from parameter.');
903✔
289
  }
290

291
  /**
292
   * Create new instances.
293
   * @constructor
294
   * @param {string} name - The descriptor name, must be a valid name.
295
   * @param {ParameterType} type - The type identifier.
296
   * @param {string} [description] - A descriptive string.
297
   * @param {boolean} [readOnly] - True indicates a parameter of this type can not be modified. Default = false.
298
   * @param {Range} [range] - An optional IntegerRange or FloatingPointRange.
299
   */
300
  constructor(
301
    name,
302
    type = ParameterType.PARAMETER_NOT_SET,
×
303
    description = 'no description',
413✔
304
    readOnly = false,
1,355✔
305
    range = null
2,151✔
306
  ) {
307
    this._name = name; // string
2,151✔
308
    this._type = type; // ParameterType
2,151✔
309
    this._description = description;
2,151✔
310
    this._readOnly = readOnly;
2,151✔
311
    this._additionalConstraints = '';
2,151✔
312
    this._range = range;
2,151✔
313

314
    this.validate();
2,151✔
315
  }
316

317
  /**
318
   * Get name.
319
   *
320
   * @return {string} - The name property.
321
   */
322
  get name() {
323
    return this._name;
23,620✔
324
  }
325

326
  /**
327
   * Get type.
328
   *
329
   * @return {ParameterType} - The type property.
330
   */
331
  get type() {
332
    return this._type;
8,625✔
333
  }
334

335
  /**
336
   * Get description.
337
   *
338
   * @return {string} - A descriptive string property.
339
   */
340
  get description() {
341
    return this._description;
12,889✔
342
  }
343

344
  /**
345
   * Get readOnly property.
346
   *
347
   * @return {boolean} - The readOnly property.
348
   */
349
  get readOnly() {
350
    return this._readOnly;
33✔
351
  }
352

353
  /**
354
   * Get additionalConstraints property.
355
   *
356
   * @return {string} - The additionalConstraints property.
357
   */
358
  get additionalConstraints() {
359
    return this._additionalConstraints;
5✔
360
  }
361

362
  /**
363
   * Set additionalConstraints property. .
364
   *
365
   * @param {string} constraintDescription - The new value.
366
   */
367
  set additionalConstraints(constraintDescription) {
368
    this._additionalConstraints = constraintDescription;
×
369
  }
370

371
  /**
372
   * Determine if rangeConstraint property has been set.
373
   *
374
   * @return {boolean} - The rangeConstraint property.
375
   */
376
  hasRange() {
377
    return !!this._range;
8,602✔
378
  }
379

380
  /**
381
   * Get range.
382
   *
383
   * @return {FloatingPointRange|IntegerRange} - The range property.
384
   */
385
  get range() {
386
    return this._range;
36✔
387
  }
388

389
  /**
390
   * Set range.
391
   * The range must be compatible with the type property.
392
   * @param {FloatingPointRange|IntegerRange} range - The new range.
393
   */
394
  set range(range) {
395
    if (!range) {
2!
396
      this._range = null;
×
397
      return;
×
398
    }
399
    if (!(range instanceof Range)) {
2!
400
      throw new TypeValidationError('range', range, 'Range', {
×
401
        entityType: 'parameter descriptor',
402
        parameterName: this.name,
403
      });
404
    }
405
    if (!range.isValidType(this.type)) {
2!
406
      throw new ParameterError(
×
407
        'Incompatible Range for parameter type',
408
        this.name,
409
        {
410
          entityType: 'parameter descriptor',
411
          details: {
412
            rangeType: range.constructor.name,
413
            parameterType: this.type,
414
          },
415
        }
416
      );
417
    }
418

419
    this._range = range;
2✔
420
  }
421

422
  /**
423
   * Check the state and ensure it is valid.
424
   * Throw a TypeError if invalid state is detected.
425
   *
426
   * @return {undefined}
427
   */
428
  validate() {
429
    // validate name
430
    if (
6,441!
431
      !this.name ||
19,323✔
432
      typeof this.name !== 'string' ||
433
      this.name.trim().length === 0
434
    ) {
435
      throw new TypeValidationError('name', this.name, 'non-empty string', {
×
436
        entityType: 'parameter descriptor',
437
      });
438
    }
439

440
    // validate type
441
    if (!validType(this.type)) {
6,441!
442
      throw new ParameterError('Invalid parameter type', this.name, {
×
443
        entityType: 'parameter descriptor',
444
        details: { providedType: this.type },
445
      });
446
    }
447

448
    // validate description
449
    if (this.description && typeof this.description !== 'string') {
6,441!
450
      throw new TypeValidationError('description', this.description, 'string', {
×
451
        entityType: 'parameter descriptor',
452
        parameterName: this.name,
453
      });
454
    }
455

456
    // validate rangeConstraint
457
    if (this.hasRange() && !this.range.isValidType(this.type)) {
6,441!
458
      throw new ParameterError(
×
459
        'Incompatible Range for parameter type',
460
        this.name,
461
        {
462
          entityType: 'parameter descriptor',
463
          details: {
464
            rangeType: this.range.constructor.name,
465
            parameterType: this.type,
466
          },
467
        }
468
      );
469
    }
470
  }
471

472
  /**
473
   * Check a parameter for consistency with this descriptor.
474
   * Throw an Error if an inconsistent state is detected.
475
   *
476
   * @param {Parameter} parameter - The parameter to test for consistency.
477
   * @return {undefined}
478
   */
479
  validateParameter(parameter) {
480
    if (!parameter) {
2,161!
481
      throw new TypeValidationError('parameter', parameter, 'Parameter', {
×
482
        entityType: 'parameter descriptor',
483
        parameterName: this.name,
484
      });
485
    }
486

487
    // ensure parameter is valid
488
    try {
2,161✔
489
      parameter.validate();
2,161✔
490
    } catch (err) {
491
      throw new ParameterError('Parameter is invalid', parameter.name, {
×
492
        cause: err,
493
        details: { validationError: err.message },
494
      });
495
    }
496

497
    // ensure this descriptor is valid
498
    try {
2,161✔
499
      this.validate();
2,161✔
500
    } catch (err) {
501
      throw new ParameterError('Descriptor is invalid', this.name, {
×
502
        entityType: 'parameter descriptor',
503
        cause: err,
504
        details: { validationError: err.message },
505
      });
506
    }
507

508
    if (this.name !== parameter.name) {
2,161!
509
      throw new ParameterError('Name mismatch', this.name, {
×
510
        details: {
511
          descriptorName: this.name,
512
          parameterName: parameter.name,
513
        },
514
      });
515
    }
516
    if (this.type !== parameter.type) {
2,161!
517
      throw new ParameterTypeError(this.name, this.type, parameter.type, {
×
518
        details: {
519
          expectedType: this.type,
520
          actualType: parameter.type,
521
        },
522
      });
523
    }
524
    if (this.hasRange() && !this.range.inRange(parameter.value)) {
2,161✔
525
      throw new RangeValidationError(
4✔
526
        'value',
527
        parameter.value,
528
        `${this.range.fromValue} to ${this.range.toValue}`,
529
        {
530
          entityType: 'parameter',
531
          parameterName: parameter.name,
532
          details: {
533
            range: {
534
              from: this.range.fromValue,
535
              to: this.range.toValue,
536
              step: this.range.step,
537
            },
538
          },
539
        }
540
      );
541
    }
542
  }
543

544
  /**
545
   * Create a rcl_interfaces.msg.ParameterDescriptor from this descriptor.
546
   *
547
   * @return {rcl_interfaces.msg.ParameterDescriptor} - The new message.
548
   */
549
  toMessage() {
550
    const msg = {
5✔
551
      name: this.name,
552
      type: this.type,
553
      description: this.description,
554
      additional_constraints: this.additionalConstraints,
555
      read_only: this.readOnly,
556
    };
557
    if (
5!
558
      (this._type === ParameterType.PARAMETER_INTEGER ||
10✔
559
        this._type === ParameterType.PARAMETER_INTEGER_ARRAY) &&
560
      this._rangeConstraint instanceof IntegerRange
561
    ) {
562
      msg.integer_range = [this._rangeConstraint];
×
563
    } else if (
5!
564
      (this._type === ParameterType.PARAMETER_DOUBLE ||
10!
565
        this._type === ParameterType.PARAMETER_DOUBLE_ARRAY) &&
566
      this._rangeConstraint instanceof FloatingPointRange
567
    ) {
568
      msg.floating_point_range = [this._rangeConstraint];
×
569
    }
570

571
    return msg;
5✔
572
  }
573
}
574

575
/**
576
 * An abstract class defining a range of numbers between 2 points inclusively
577
 * divided by a step value.
578
 * @class
579
 */
580
class Range {
581
  /**
582
   * Create a new instance.
583
   * @constructor
584
   * @param {number} fromValue - The lowest inclusive value in range
585
   * @param {number} toValue - The highest inclusive value in range
586
   * @param {number} step - The internal unit size.
587
   */
588
  constructor(fromValue, toValue, step = 1) {
×
589
    this._fromValue = fromValue;
4✔
590
    this._toValue = toValue;
4✔
591
    this._step = step;
4✔
592
  }
593

594
  /**
595
   * Get fromValue.
596
   *
597
   * @return {number} - The lowest inclusive value in range.
598
   */
599
  get fromValue() {
600
    return this._fromValue;
43✔
601
  }
602

603
  /**
604
   * Get toValue.
605
   *
606
   * @return {number} - The highest inclusive value in range.
607
   */
608
  get toValue() {
609
    return this._toValue;
43✔
610
  }
611

612
  /**
613
   * Get step unit.
614
   *
615
   * @return {number} - The internal unit size.
616
   */
617
  get step() {
618
    return this._step;
35✔
619
  }
620

621
  /**
622
   * Determine if a value is within this range.
623
   * A TypeError is thrown when value is not a number or bigint.
624
   * Subclasses should override and call this method for basic type checking.
625
   *
626
   * @param {number|bigint} value - The number or bigint to check.
627
   * @return {boolean} - True if value satisfies the range; false otherwise.
628
   */
629
  inRange(value) {
630
    if (Array.isArray(value)) {
9!
631
      const valArr = value;
×
632
      return valArr.reduce(
×
633
        (inRange, val) => inRange && this.inRange(val),
×
634
        true
635
      );
636
    } else if (typeof value !== 'number' && typeof value !== 'bigint') {
9!
637
      throw new TypeValidationError('value', value, 'number or bigint', {
×
638
        entityType: 'range',
639
      });
640
    }
641

642
    return true;
9✔
643
  }
644

645
  /**
646
   * Abstract method that determines if a ParameterType is compatible.
647
   * Subclasses must implement this method.
648
   *
649
   * @param {ParameterType} parameterType - The parameter type to test.
650
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
651
   */
652
  // eslint-disable-next-line no-unused-vars
653
  isValidType(parameterType) {
654
    return false;
×
655
  }
656
}
657

658
/**
659
 * Defines a range for floating point values.
660
 * @class
661
 */
662
class FloatingPointRange extends Range {
663
  /**
664
   * Create a new instance.
665
   * @constructor
666
   * @param {number} fromValue - The lowest inclusive value in range
667
   * @param {number} toValue - The highest inclusive value in range
668
   * @param {number} step - The internal unit size.
669
   * @param {number} tolerance - The plus/minus tolerance for number equivalence.
670
   */
671
  constructor(
672
    fromValue,
673
    toValue,
674
    step = 1,
×
675
    tolerance = DEFAULT_NUMERIC_RANGE_TOLERANCE
1✔
676
  ) {
677
    super(fromValue, toValue, step);
1✔
678
    this._tolerance = tolerance;
1✔
679
  }
680

681
  get tolerance() {
682
    return this._tolerance;
20✔
683
  }
684

685
  /**
686
   * Determine if a ParameterType is compatible.
687
   *
688
   * @param {ParameterType} parameterType - The parameter type to test.
689
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
690
   */
691
  isValidType(parameterType) {
692
    const result =
693
      parameterType === ParameterType.PARAMETER_DOUBLE ||
×
694
      parameterType === ParameterType.PARAMETER_DOUBLE_ARRAY;
695
    return result;
×
696
  }
697

698
  /**
699
   * Determine if a value is within this range.
700
   * A TypeError is thrown when value is not a number.
701
   *
702
   * @param {number} value - The number to check.
703
   * @return {boolean} - True if value satisfies the range; false otherwise.
704
   */
705
  inRange(value) {
706
    if (!super.inRange(value)) return false;
9!
707

708
    const min = Math.min(this.fromValue, this.toValue);
9✔
709
    const max = Math.max(this.fromValue, this.toValue);
9✔
710

711
    if (
9✔
712
      isClose(value, min, this.tolerance) ||
17✔
713
      isClose(value, max, this.tolerance)
714
    ) {
715
      return true;
4✔
716
    }
717
    if (value < min || value > max) {
5✔
718
      return false;
2✔
719
    }
720
    if (this.step != 0.0) {
3!
721
      const distanceInSteps = Math.round((value - min) / this.step);
3✔
722
      if (!isClose(min + distanceInSteps * this.step, value, this.tolerance)) {
3!
723
        return false;
×
724
      }
725
    }
726

727
    return true;
3✔
728
  }
729
}
730

731
/**
732
 * Defines a range for integer values.
733
 * @class
734
 */
735
class IntegerRange extends Range {
736
  /**
737
   * Create a new instance.
738
   * @constructor
739
   * @param {bigint} fromValue - The lowest inclusive value in range
740
   * @param {bigint} toValue - The highest inclusive value in range
741
   * @param {bigint} step - The internal unit size.
742
   */
743
  constructor(fromValue, toValue, step = 1n) {
1✔
744
    super(fromValue, toValue, step);
3✔
745
  }
746

747
  /**
748
   * Determine if a ParameterType is compatible.
749
   *
750
   * @param {ParameterType} parameterType - The parameter type to test.
751
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
752
   */
753
  isValidType(parameterType) {
754
    const result =
755
      parameterType === ParameterType.PARAMETER_INTEGER ||
10!
756
      parameterType === ParameterType.PARAMETER_INTEGER_ARRAY;
757
    return result;
10✔
758
  }
759

760
  inRange(value) {
761
    const min = this.fromValue;
15✔
762
    const max = this.toValue;
15✔
763
    if (value < min || value > max) {
15✔
764
      return false;
5✔
765
    }
766

767
    if (this.step != 0n && (value - min) % this.step !== 0n) {
10✔
768
      return false;
1✔
769
    }
770
    return true;
9✔
771
  }
772
}
773

774
/**
775
 * Infer a ParameterType for JS primitive types:
776
 * string, boolean, number and arrays of these types.
777
 * A TypeError is thrown for a value who's type is not one of
778
 * the set listed.
779
 * @param {any} value - The value to infer it's ParameterType
780
 * @returns {ParameterType} - The ParameterType that best scribes the value.
781
 */
782
function parameterTypeFromValue(value) {
783
  if (value === null || value === undefined) {
11!
784
    return ParameterType.PARAMETER_NOT_SET;
×
785
  }
786

787
  if (typeof value === 'boolean') {
11✔
788
    return ParameterType.PARAMETER_BOOL;
2✔
789
  }
790

791
  if (typeof value === 'string') {
9✔
792
    return ParameterType.PARAMETER_STRING;
2✔
793
  }
794

795
  if (typeof value === 'bigint') {
7!
796
    return ParameterType.PARAMETER_INTEGER;
×
797
  }
798

799
  if (typeof value === 'number') {
7✔
800
    // Distinguish between integer and double
801
    return Number.isInteger(value)
5✔
802
      ? ParameterType.PARAMETER_INTEGER
803
      : ParameterType.PARAMETER_DOUBLE;
804
  }
805

806
  // Handle TypedArrays
807
  if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
2!
808
    if (value instanceof Uint8Array) {
2!
809
      return ParameterType.PARAMETER_BYTE_ARRAY;
2✔
810
    }
811
    // For other typed arrays, infer from first element
812
    if (value.length > 0) {
×
813
      const firstType = parameterTypeFromValue(value[0]);
×
814
      if (firstType === ParameterType.PARAMETER_INTEGER) {
×
815
        return ParameterType.PARAMETER_INTEGER_ARRAY;
×
816
      }
817
      return ParameterType.PARAMETER_DOUBLE_ARRAY;
×
818
    }
819
    return ParameterType.PARAMETER_NOT_SET;
×
820
  }
821

822
  if (Array.isArray(value)) {
×
823
    if (value.length === 0) {
×
824
      return ParameterType.PARAMETER_NOT_SET;
×
825
    }
826

827
    const elementType = parameterTypeFromValue(value[0]);
×
828
    switch (elementType) {
×
829
      case ParameterType.PARAMETER_BOOL:
830
        return ParameterType.PARAMETER_BOOL_ARRAY;
×
831
      case ParameterType.PARAMETER_INTEGER:
832
        return ParameterType.PARAMETER_INTEGER_ARRAY;
×
833
      case ParameterType.PARAMETER_DOUBLE:
834
        return ParameterType.PARAMETER_DOUBLE_ARRAY;
×
835
      case ParameterType.PARAMETER_STRING:
836
        return ParameterType.PARAMETER_STRING_ARRAY;
×
837
      default:
838
        return ParameterType.PARAMETER_NOT_SET;
×
839
    }
840
  }
841

842
  // Unrecognized value type
843
  throw new TypeValidationError('value', value, 'valid parameter type', {
×
844
    entityType: 'parameter',
845
    details: {
846
      supportedTypes: [
847
        'boolean',
848
        'string',
849
        'number',
850
        'boolean[]',
851
        'number[]',
852
        'string[]',
853
      ],
854
    },
855
  });
856
}
857

858
/**
859
 * Determine if a number maps to is a valid ParameterType.
860
 *
861
 * @param {ParameterType} parameterType - The value to test.
862
 * @return {boolean} - True if value is a valid ParameterType; false otherwise.
863
 */
864
function validType(parameterType) {
865
  let result =
866
    typeof parameterType === 'number' &&
12,999✔
867
    ParameterType.PARAMETER_NOT_SET <=
868
      parameterType <=
869
      ParameterType.PARAMETER_STRING_ARRAY;
870

871
  return result;
12,999✔
872
}
873

874
/**
875
 * Test if value can be represented by a ParameterType.
876
 *
877
 * @param {number} value - The value to test.
878
 * @param {ParameterType} type - The ParameterType to test value against.
879
 * @return {boolean} - True if value can be represented by type.
880
 */
881
function validValue(value, type) {
882
  if (value == null) {
8,279✔
883
    return type === ParameterType.PARAMETER_NOT_SET;
6✔
884
  }
885

886
  let result = true;
8,273✔
887
  switch (type) {
8,273!
888
    case ParameterType.PARAMETER_NOT_SET:
889
      result = !value;
×
890
      break;
×
891
    case ParameterType.PARAMETER_BOOL:
892
      result = typeof value === 'boolean';
4,809✔
893
      break;
4,809✔
894
    case ParameterType.PARAMETER_STRING:
895
      result = typeof value === 'string';
789✔
896
      break;
789✔
897
    case ParameterType.PARAMETER_DOUBLE:
898
    case PARAMETER_BYTE:
899
      result = typeof value === 'number';
1,182✔
900
      break;
1,182✔
901
    case ParameterType.PARAMETER_INTEGER:
902
      result = typeof value === 'bigint';
872✔
903
      break;
872✔
904
    case ParameterType.PARAMETER_BOOL_ARRAY:
905
    case ParameterType.PARAMETER_BYTE_ARRAY:
906
    case ParameterType.PARAMETER_INTEGER_ARRAY:
907
    case ParameterType.PARAMETER_DOUBLE_ARRAY:
908
    case ParameterType.PARAMETER_STRING_ARRAY:
909
      result = _validArray(value, type);
621✔
910
      break;
621✔
911
    default:
912
      result = false;
×
913
  }
914

915
  return result;
8,273✔
916
}
917

918
function _validArray(values, type) {
919
  if (!Array.isArray(values)) return false;
621!
920

921
  let arrayElementType;
922
  if (type === ParameterType.PARAMETER_BOOL_ARRAY) {
621✔
923
    arrayElementType = ParameterType.PARAMETER_BOOL;
2✔
924
  } else if (type === ParameterType.PARAMETER_BYTE_ARRAY) {
619✔
925
    arrayElementType = PARAMETER_BYTE;
154✔
926
  }
927
  if (type === ParameterType.PARAMETER_INTEGER_ARRAY) {
621✔
928
    arrayElementType = ParameterType.PARAMETER_INTEGER;
173✔
929
  }
930
  if (type === ParameterType.PARAMETER_DOUBLE_ARRAY) {
621✔
931
    arrayElementType = ParameterType.PARAMETER_DOUBLE;
146✔
932
  }
933
  if (type === ParameterType.PARAMETER_STRING_ARRAY) {
621✔
934
    arrayElementType = ParameterType.PARAMETER_STRING;
146✔
935
  }
936

937
  return values.reduce(
621✔
938
    (compatible, val) =>
939
      compatible &&
1,721✔
940
      !_isArrayParameterType(arrayElementType) &&
941
      validValue(val, arrayElementType),
942
    true
943
  );
944
}
945

946
function _isArrayParameterType(type) {
947
  return (
1,721✔
948
    type === ParameterType.PARAMETER_BOOL_ARRAY ||
8,605✔
949
    type === ParameterType.PARAMETER_BYTE_ARRAY ||
950
    type === ParameterType.PARAMETER_INTEGER_ARRAY ||
951
    type === ParameterType.PARAMETER_DOUBLE_ARRAY ||
952
    type === ParameterType.PARAMETER_STRING_ARRAY
953
  );
954
}
955

956
module.exports = {
26✔
957
  ParameterType,
958
  Parameter,
959
  ParameterDescriptor,
960
  PARAMETER_SEPARATOR,
961
  Range,
962
  FloatingPointRange,
963
  IntegerRange,
964
  DEFAULT_NUMERIC_RANGE_TOLERANCE,
965
  parameterTypeFromValue,
966
};
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