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

RobotWebTools / rclnodejs / 19071410104

04 Nov 2025 02:08PM UTC coverage: 83.072% (+0.4%) from 82.711%
19071410104

Pull #1320

github

web-flow
Merge 9cad4567e into 3ad842cc4
Pull Request #1320: feat: add structured error handling with class error hierarchy

1032 of 1365 branches covered (75.6%)

Branch coverage included in aggregate %.

161 of 239 new or added lines in 25 files covered. (67.36%)

29 existing lines in 1 file now uncovered.

2354 of 2711 relevant lines covered (86.83%)

459.93 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;
1,926✔
131
    this._type = type;
1,926✔
132
    // Convert to bigint if it's type of `PARAMETER_INTEGER`.
133
    this._value =
1,926✔
134
      this._type == ParameterType.PARAMETER_INTEGER ? BigInt(value) : value;
1,926✔
135
    this._isDirty = true;
1,926✔
136

137
    this.validate();
1,926✔
138
  }
139

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

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

158
  /**
159
   * Get value.
160
   *
161
   * @return {any} - The parameter value.
162
   */
163
  get value() {
164
    return this._value;
9,010✔
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 (
5,669!
189
      !this.name ||
17,007✔
190
      typeof this.name !== 'string' ||
191
      this.name.trim().length === 0
192
    ) {
NEW
193
      throw new TypeValidationError('name', this.name, 'non-empty string', {
×
194
        entityType: 'parameter',
195
      });
196
    }
197

198
    // validate type
199
    if (!validType(this.type)) {
5,669!
NEW
200
      throw new ParameterTypeError(
×
201
        this.name,
202
        'Invalid parameter type',
203
        this.type,
204
        {
205
          details: { providedType: this.type },
206
        }
207
      );
208
    }
209

210
    // validate value
211
    if (!validValue(this.value, this.type)) {
5,669✔
212
      throw new ParameterTypeError(
1✔
213
        this.name,
214
        'Incompatible value for parameter type',
215
        typeof this.value,
216
        {
217
          details: {
218
            expectedType: this.type,
219
            providedValue: this.value,
220
          },
221
        }
222
      );
223
    }
224

225
    this._dirty = false;
5,668✔
226
  }
227

228
  /**
229
   * Create a rcl_interfaces.msg.Parameter from this instance.
230
   *
231
   * @return {rcl_interfaces.msg.Parameter} - The new instance.
232
   */
233
  toParameterMessage() {
234
    const msg = {
1,860✔
235
      name: this.name,
236
      value: this.toParameterValueMessage(),
237
    };
238
    return msg;
1,860✔
239
  }
240

241
  /**
242
   * Create a rcl_interfaces.msg.ParameterValue from this instance.
243
   *
244
   * @return {rcl_interfaces.msg.ParameterValue} - The new instance.
245
   */
246
  toParameterValueMessage() {
247
    const msg = {};
1,889✔
248
    msg.type = this.type;
1,889✔
249
    switch (this.type) {
1,889!
250
      case ParameterType.PARAMETER_NOT_SET:
251
        break;
3✔
252
      case ParameterType.PARAMETER_BOOL:
253
        msg.bool_value = this.value;
1,435✔
254
        break;
1,435✔
255
      case ParameterType.PARAMETER_BOOL_ARRAY:
UNCOV
256
        msg.bool_array_value = this.value;
×
UNCOV
257
        break;
×
258
      case ParameterType.PARAMETER_BYTE_ARRAY:
259
        msg.byte_array_value = this.value.map((val) => Math.trunc(val));
159✔
260
        break;
53✔
261
      case ParameterType.PARAMETER_DOUBLE:
262
        msg.double_value = this.value;
53✔
263
        break;
53✔
264
      case ParameterType.PARAMETER_DOUBLE_ARRAY:
265
        msg.double_array_value = this.value;
49✔
266
        break;
49✔
267
      case ParameterType.PARAMETER_INTEGER:
268
        msg.integer_value = this.value;
65✔
269
        break;
65✔
270
      case ParameterType.PARAMETER_INTEGER_ARRAY:
271
        msg.integer_array_value = this.value;
58✔
272
        break;
58✔
273
      case ParameterType.PARAMETER_STRING:
274
        msg.string_value = this.value;
124✔
275
        break;
124✔
276
      case ParameterType.PARAMETER_STRING_ARRAY:
277
        msg.string_array_value = this.value;
49✔
278
        break;
49✔
279
    }
280

281
    return msg;
1,889✔
282
  }
283
}
284

285
/**
286
 * A node parameter descriptor.
287
 * @class
288
 */
289
class ParameterDescriptor {
290
  /**
291
   * Create a new instance from a parameter.
292
   * @constructs
293
   * @param {Parameter} parameter - The parameter from which new instance is constructed.
294
   * @return {ParameterDescriptor} - The new instance.
295
   */
296
  static fromParameter(parameter) {
297
    const name = parameter.name;
699✔
298
    const type = parameter.type;
699✔
299
    return new ParameterDescriptor(name, type, 'Created from parameter.');
699✔
300
  }
301

302
  /**
303
   * Create new instances.
304
   * @constructor
305
   * @param {string} name - The descriptor name, must be a valid name.
306
   * @param {ParameterType} type - The type identifier.
307
   * @param {string} [description] - A descriptive string.
308
   * @param {boolean} [readOnly] - True indicates a parameter of this type can not be modified. Default = false.
309
   * @param {Range} [range] - An optional IntegerRange or FloatingPointRange.
310
   */
311
  constructor(
312
    name,
313
    type = ParameterType.PARAMETER_NOT_SET,
×
314
    description = 'no description',
413✔
315
    readOnly = false,
1,151✔
316
    range = null
1,864✔
317
  ) {
318
    this._name = name; // string
1,864✔
319
    this._type = type; // ParameterType
1,864✔
320
    this._description = description;
1,864✔
321
    this._readOnly = readOnly;
1,864✔
322
    this._additionalConstraints = '';
1,864✔
323
    this._range = range;
1,864✔
324

325
    this.validate();
1,864✔
326
  }
327

328
  /**
329
   * Get name.
330
   *
331
   * @return {string} - The name property.
332
   */
333
  get name() {
334
    return this._name;
20,431✔
335
  }
336

337
  /**
338
   * Get type.
339
   *
340
   * @return {ParameterType} - The type property.
341
   */
342
  get type() {
343
    return this._type;
7,461✔
344
  }
345

346
  /**
347
   * Get description.
348
   *
349
   * @return {string} - A descriptive string property.
350
   */
351
  get description() {
352
    return this._description;
11,151✔
353
  }
354

355
  /**
356
   * Get readOnly property.
357
   *
358
   * @return {boolean} - The readOnly property.
359
   */
360
  get readOnly() {
361
    return this._readOnly;
25✔
362
  }
363

364
  /**
365
   * Get additionalConstraints property.
366
   *
367
   * @return {string} - The additionalConstraints property.
368
   */
369
  get additionalConstraints() {
370
    return this._additionalConstraints;
5✔
371
  }
372

373
  /**
374
   * Set additionalConstraints property. .
375
   *
376
   * @param {string} constraintDescription - The new value.
377
   */
378
  set additionalConstraints(constraintDescription) {
UNCOV
379
    this._additionalConstraints = constraintDescription;
×
380
  }
381

382
  /**
383
   * Determine if rangeConstraint property has been set.
384
   *
385
   * @return {boolean} - The rangeConstraint property.
386
   */
387
  hasRange() {
388
    return !!this._range;
7,438✔
389
  }
390

391
  /**
392
   * Get range.
393
   *
394
   * @return {FloatingPointRange|IntegerRange} - The range property.
395
   */
396
  get range() {
397
    return this._range;
36✔
398
  }
399

400
  /**
401
   * Set range.
402
   * The range must be compatible with the type property.
403
   * @param {FloatingPointRange|IntegerRange} range - The new range.
404
   */
405
  set range(range) {
406
    if (!range) {
2!
407
      this._range = null;
×
408
      return;
×
409
    }
410
    if (!(range instanceof Range)) {
2!
NEW
411
      throw new TypeValidationError('range', range, 'Range', {
×
412
        entityType: 'parameter descriptor',
413
        parameterName: this.name,
414
      });
415
    }
416
    if (!range.isValidType(this.type)) {
2!
NEW
UNCOV
417
      throw new ParameterTypeError(
×
418
        this.name,
419
        'Incompatible Range for parameter type',
420
        this.type,
421
        {
422
          entityType: 'parameter descriptor',
423
          details: {
424
            rangeType: range.constructor.name,
425
            parameterType: this.type,
426
          },
427
        }
428
      );
429
    }
430

431
    this._range = range;
2✔
432
  }
433

434
  /**
435
   * Check the state and ensure it is valid.
436
   * Throw a TypeError if invalid state is detected.
437
   *
438
   * @return {undefined}
439
   */
440
  validate() {
441
    // validate name
442
    if (
5,572!
443
      !this.name ||
16,716✔
444
      typeof this.name !== 'string' ||
445
      this.name.trim().length === 0
446
    ) {
NEW
UNCOV
447
      throw new TypeValidationError('name', this.name, 'non-empty string', {
×
448
        entityType: 'parameter descriptor',
449
      });
450
    }
451

452
    // validate type
453
    if (!validType(this.type)) {
5,572!
NEW
UNCOV
454
      throw new ParameterTypeError(
×
455
        this.name,
456
        'Invalid parameter type',
457
        this.type,
458
        {
459
          entityType: 'parameter descriptor',
460
          details: { providedType: this.type },
461
        }
462
      );
463
    }
464

465
    // validate description
466
    if (this.description && typeof this.description !== 'string') {
5,572!
NEW
467
      throw new TypeValidationError('description', this.description, 'string', {
×
468
        entityType: 'parameter descriptor',
469
        parameterName: this.name,
470
      });
471
    }
472

473
    // validate rangeConstraint
474
    if (this.hasRange() && !this.range.isValidType(this.type)) {
5,572!
NEW
UNCOV
475
      throw new ParameterTypeError(
×
476
        this.name,
477
        'Incompatible Range for parameter type',
478
        this.type,
479
        {
480
          entityType: 'parameter descriptor',
481
          details: {
482
            rangeType: this.range.constructor.name,
483
            parameterType: this.type,
484
          },
485
        }
486
      );
487
    }
488
  }
489

490
  /**
491
   * Check a parameter for consistency with this descriptor.
492
   * Throw an Error if an inconsistent state is detected.
493
   *
494
   * @param {Parameter} parameter - The parameter to test for consistency.
495
   * @return {undefined}
496
   */
497
  validateParameter(parameter) {
498
    if (!parameter) {
1,866!
NEW
UNCOV
499
      throw new TypeValidationError('parameter', parameter, 'Parameter', {
×
500
        entityType: 'parameter descriptor',
501
        parameterName: this.name,
502
      });
503
    }
504

505
    // ensure parameter is valid
506
    try {
1,866✔
507
      parameter.validate();
1,866✔
508
    } catch (err) {
NEW
509
      throw new ParameterError(
×
510
        parameter.name,
511
        'Parameter is invalid',
512
        undefined,
513
        {
514
          cause: err,
515
          details: { validationError: err.message },
516
        }
517
      );
518
    }
519

520
    // ensure this descriptor is valid
521
    try {
1,866✔
522
      this.validate();
1,866✔
523
    } catch (err) {
NEW
UNCOV
524
      throw new ParameterError(this.name, 'Descriptor is invalid', undefined, {
×
525
        entityType: 'parameter descriptor',
526
        cause: err,
527
        details: { validationError: err.message },
528
      });
529
    }
530

531
    if (this.name !== parameter.name) {
1,866!
NEW
532
      throw new ParameterError(this.name, 'Name mismatch', undefined, {
×
533
        details: {
534
          descriptorName: this.name,
535
          parameterName: parameter.name,
536
        },
537
      });
538
    }
539
    if (this.type !== parameter.type) {
1,866!
NEW
540
      throw new ParameterTypeError(this.name, 'Type mismatch', this.type, {
×
541
        details: {
542
          descriptorType: this.type,
543
          parameterType: parameter.type,
544
        },
545
      });
546
    }
547
    if (this.hasRange() && !this.range.inRange(parameter.value)) {
1,866✔
548
      throw new RangeValidationError(
4✔
549
        'value',
550
        parameter.value,
551
        `${this.range.fromValue} to ${this.range.toValue}`,
552
        {
553
          entityType: 'parameter',
554
          parameterName: parameter.name,
555
          details: {
556
            range: {
557
              from: this.range.fromValue,
558
              to: this.range.toValue,
559
              step: this.range.step,
560
            },
561
          },
562
        }
563
      );
564
    }
565
  }
566

567
  /**
568
   * Create a rcl_interfaces.msg.ParameterDescriptor from this descriptor.
569
   *
570
   * @return {rcl_interfaces.msg.ParameterDescriptor} - The new message.
571
   */
572
  toMessage() {
573
    const msg = {
5✔
574
      name: this.name,
575
      type: this.type,
576
      description: this.description,
577
      additional_constraints: this.additionalConstraints,
578
      read_only: this.readOnly,
579
    };
580
    if (
5!
581
      (this._type === ParameterType.PARAMETER_INTEGER ||
10✔
582
        this._type === ParameterType.PARAMETER_INTEGER_ARRAY) &&
583
      this._rangeConstraint instanceof IntegerRange
584
    ) {
UNCOV
585
      msg.integer_range = [this._rangeConstraint];
×
586
    } else if (
5!
587
      (this._type === ParameterType.PARAMETER_DOUBLE ||
10!
588
        this._type === ParameterType.PARAMETER_DOUBLE_ARRAY) &&
589
      this._rangeConstraint instanceof FloatingPointRange
590
    ) {
UNCOV
591
      msg.floating_point_range = [this._rangeConstraint];
×
592
    }
593

594
    return msg;
5✔
595
  }
596
}
597

598
/**
599
 * An abstract class defining a range of numbers between 2 points inclusively
600
 * divided by a step value.
601
 * @class
602
 */
603
class Range {
604
  /**
605
   * Create a new instance.
606
   * @constructor
607
   * @param {number} fromValue - The lowest inclusive value in range
608
   * @param {number} toValue - The highest inclusive value in range
609
   * @param {number} step - The internal unit size.
610
   */
611
  constructor(fromValue, toValue, step = 1) {
×
612
    this._fromValue = fromValue;
4✔
613
    this._toValue = toValue;
4✔
614
    this._step = step;
4✔
615
  }
616

617
  /**
618
   * Get fromValue.
619
   *
620
   * @return {number} - The lowest inclusive value in range.
621
   */
622
  get fromValue() {
623
    return this._fromValue;
43✔
624
  }
625

626
  /**
627
   * Get toValue.
628
   *
629
   * @return {number} - The highest inclusive value in range.
630
   */
631
  get toValue() {
632
    return this._toValue;
43✔
633
  }
634

635
  /**
636
   * Get step unit.
637
   *
638
   * @return {number} - The internal unit size.
639
   */
640
  get step() {
641
    return this._step;
35✔
642
  }
643

644
  /**
645
   * Determine if a value is within this range.
646
   * A TypeError is thrown when value is not a number or bigint.
647
   * Subclasses should override and call this method for basic type checking.
648
   *
649
   * @param {number|bigint} value - The number or bigint to check.
650
   * @return {boolean} - True if value satisfies the range; false otherwise.
651
   */
652
  inRange(value) {
653
    if (Array.isArray(value)) {
9!
654
      const valArr = value;
×
UNCOV
655
      return valArr.reduce(
×
UNCOV
656
        (inRange, val) => inRange && this.inRange(val),
×
657
        true
658
      );
659
    } else if (typeof value !== 'number' && typeof value !== 'bigint') {
9!
NEW
UNCOV
660
      throw new TypeValidationError('value', value, 'number or bigint', {
×
661
        entityType: 'range',
662
      });
663
    }
664

665
    return true;
9✔
666
  }
667

668
  /**
669
   * Abstract method that determines if a ParameterType is compatible.
670
   * Subclasses must implement this method.
671
   *
672
   * @param {ParameterType} parameterType - The parameter type to test.
673
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
674
   */
675
  // eslint-disable-next-line no-unused-vars
676
  isValidType(parameterType) {
UNCOV
677
    return false;
×
678
  }
679
}
680

681
/**
682
 * Defines a range for floating point values.
683
 * @class
684
 */
685
class FloatingPointRange extends Range {
686
  /**
687
   * Create a new instance.
688
   * @constructor
689
   * @param {number} fromValue - The lowest inclusive value in range
690
   * @param {number} toValue - The highest inclusive value in range
691
   * @param {number} step - The internal unit size.
692
   * @param {number} tolerance - The plus/minus tolerance for number equivalence.
693
   */
694
  constructor(
695
    fromValue,
696
    toValue,
697
    step = 1,
×
698
    tolerance = DEFAULT_NUMERIC_RANGE_TOLERANCE
1✔
699
  ) {
700
    super(fromValue, toValue, step);
1✔
701
    this._tolerance = tolerance;
1✔
702
  }
703

704
  get tolerance() {
705
    return this._tolerance;
20✔
706
  }
707

708
  /**
709
   * Determine if a ParameterType is compatible.
710
   *
711
   * @param {ParameterType} parameterType - The parameter type to test.
712
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
713
   */
714
  isValidType(parameterType) {
715
    const result =
UNCOV
716
      parameterType === ParameterType.PARAMETER_DOUBLE ||
×
717
      parameterType === ParameterType.PARAMETER_DOUBLE_ARRAY;
UNCOV
718
    return result;
×
719
  }
720

721
  /**
722
   * Determine if a value is within this range.
723
   * A TypeError is thrown when value is not a number.
724
   *
725
   * @param {number} value - The number to check.
726
   * @return {boolean} - True if value satisfies the range; false otherwise.
727
   */
728
  inRange(value) {
729
    if (!super.inRange(value)) return false;
9!
730

731
    const min = Math.min(this.fromValue, this.toValue);
9✔
732
    const max = Math.max(this.fromValue, this.toValue);
9✔
733

734
    if (
9✔
735
      isClose(value, min, this.tolerance) ||
17✔
736
      isClose(value, max, this.tolerance)
737
    ) {
738
      return true;
4✔
739
    }
740
    if (value < min || value > max) {
5✔
741
      return false;
2✔
742
    }
743
    if (this.step != 0.0) {
3!
744
      const distanceInSteps = Math.round((value - min) / this.step);
3✔
745
      if (!isClose(min + distanceInSteps * this.step, value, this.tolerance)) {
3!
UNCOV
746
        return false;
×
747
      }
748
    }
749

750
    return true;
3✔
751
  }
752
}
753

754
/**
755
 * Defines a range for integer values.
756
 * @class
757
 */
758
class IntegerRange extends Range {
759
  /**
760
   * Create a new instance.
761
   * @constructor
762
   * @param {bigint} fromValue - The lowest inclusive value in range
763
   * @param {bigint} toValue - The highest inclusive value in range
764
   * @param {bigint} step - The internal unit size.
765
   */
766
  constructor(fromValue, toValue, step = 1n) {
1✔
767
    super(fromValue, toValue, step);
3✔
768
  }
769

770
  /**
771
   * Determine if a ParameterType is compatible.
772
   *
773
   * @param {ParameterType} parameterType - The parameter type to test.
774
   * @return {boolean} - True if parameterType is compatible; otherwise return false.
775
   */
776
  isValidType(parameterType) {
777
    const result =
778
      parameterType === ParameterType.PARAMETER_INTEGER ||
10!
779
      parameterType === ParameterType.PARAMETER_INTEGER_ARRAY;
780
    return result;
10✔
781
  }
782

783
  inRange(value) {
784
    const min = this.fromValue;
15✔
785
    const max = this.toValue;
15✔
786
    if (value < min || value > max) {
15✔
787
      return false;
5✔
788
    }
789

790
    if (this.step != 0n && (value - min) % this.step !== 0n) {
10✔
791
      return false;
1✔
792
    }
793
    return true;
9✔
794
  }
795
}
796

797
/**
798
 * Infer a ParameterType for JS primitive types:
799
 * string, boolean, number and arrays of these types.
800
 * A TypeError is thrown for a value who's type is not one of
801
 * the set listed.
802
 * @param {any} value - The value to infer it's ParameterType
803
 * @returns {ParameterType} - The ParameterType that best scribes the value.
804
 */
805
function parameterTypeFromValue(value) {
806
  if (value === null || value === undefined) {
11!
UNCOV
807
    return ParameterType.PARAMETER_NOT_SET;
×
808
  }
809

810
  if (typeof value === 'boolean') {
11✔
811
    return ParameterType.PARAMETER_BOOL;
2✔
812
  }
813

814
  if (typeof value === 'string') {
9✔
815
    return ParameterType.PARAMETER_STRING;
2✔
816
  }
817

818
  if (typeof value === 'bigint') {
7!
819
    return ParameterType.PARAMETER_INTEGER;
×
820
  }
821

822
  if (typeof value === 'number') {
7✔
823
    // Distinguish between integer and double
824
    return Number.isInteger(value)
5✔
825
      ? ParameterType.PARAMETER_INTEGER
826
      : ParameterType.PARAMETER_DOUBLE;
827
  }
828

829
  // Handle TypedArrays
830
  if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
2!
831
    if (value instanceof Uint8Array) {
2!
832
      return ParameterType.PARAMETER_BYTE_ARRAY;
2✔
833
    }
834
    // For other typed arrays, infer from first element
UNCOV
835
    if (value.length > 0) {
×
836
      const firstType = parameterTypeFromValue(value[0]);
×
UNCOV
837
      if (firstType === ParameterType.PARAMETER_INTEGER) {
×
838
        return ParameterType.PARAMETER_INTEGER_ARRAY;
×
839
      }
UNCOV
840
      return ParameterType.PARAMETER_DOUBLE_ARRAY;
×
841
    }
UNCOV
842
    return ParameterType.PARAMETER_NOT_SET;
×
843
  }
844

845
  if (Array.isArray(value)) {
×
846
    if (value.length === 0) {
×
847
      return ParameterType.PARAMETER_NOT_SET;
×
848
    }
849

850
    const elementType = parameterTypeFromValue(value[0]);
×
851
    switch (elementType) {
×
852
      case ParameterType.PARAMETER_BOOL:
853
        return ParameterType.PARAMETER_BOOL_ARRAY;
×
854
      case ParameterType.PARAMETER_INTEGER:
855
        return ParameterType.PARAMETER_INTEGER_ARRAY;
×
856
      case ParameterType.PARAMETER_DOUBLE:
UNCOV
857
        return ParameterType.PARAMETER_DOUBLE_ARRAY;
×
858
      case ParameterType.PARAMETER_STRING:
UNCOV
859
        return ParameterType.PARAMETER_STRING_ARRAY;
×
860
      default:
UNCOV
861
        return ParameterType.PARAMETER_NOT_SET;
×
862
    }
863
  }
864

865
  // Unrecognized value type
NEW
UNCOV
866
  throw new TypeValidationError('value', value, 'valid parameter type', {
×
867
    entityType: 'parameter',
868
    details: {
869
      supportedTypes: [
870
        'boolean',
871
        'string',
872
        'number',
873
        'boolean[]',
874
        'number[]',
875
        'string[]',
876
      ],
877
    },
878
  });
879
}
880

881
/**
882
 * Determine if a number maps to is a valid ParameterType.
883
 *
884
 * @param {ParameterType} parameterType - The value to test.
885
 * @return {boolean} - True if value is a valid ParameterType; false otherwise.
886
 */
887
function validType(parameterType) {
888
  let result =
889
    typeof parameterType === 'number' &&
11,241✔
890
    ParameterType.PARAMETER_NOT_SET <=
891
      parameterType <=
892
      ParameterType.PARAMETER_STRING_ARRAY;
893

894
  return result;
11,241✔
895
}
896

897
/**
898
 * Test if value can be represented by a ParameterType.
899
 *
900
 * @param {number} value - The value to test.
901
 * @param {ParameterType} type - The ParameterType to test value against.
902
 * @return {boolean} - True if value can be represented by type.
903
 */
904
function validValue(value, type) {
905
  if (value == null) {
7,390✔
906
    return type === ParameterType.PARAMETER_NOT_SET;
6✔
907
  }
908

909
  let result = true;
7,384✔
910
  switch (type) {
7,384!
911
    case ParameterType.PARAMETER_NOT_SET:
912
      result = !value;
×
UNCOV
913
      break;
×
914
    case ParameterType.PARAMETER_BOOL:
915
      result = typeof value === 'boolean';
4,311✔
916
      break;
4,311✔
917
    case ParameterType.PARAMETER_STRING:
918
      result = typeof value === 'string';
663✔
919
      break;
663✔
920
    case ParameterType.PARAMETER_DOUBLE:
921
    case PARAMETER_BYTE:
922
      result = typeof value === 'number';
1,057✔
923
      break;
1,057✔
924
    case ParameterType.PARAMETER_INTEGER:
925
      result = typeof value === 'bigint';
732✔
926
      break;
732✔
927
    case ParameterType.PARAMETER_BOOL_ARRAY:
928
    case ParameterType.PARAMETER_BYTE_ARRAY:
929
    case ParameterType.PARAMETER_INTEGER_ARRAY:
930
    case ParameterType.PARAMETER_DOUBLE_ARRAY:
931
    case ParameterType.PARAMETER_STRING_ARRAY:
932
      result = _validArray(value, type);
621✔
933
      break;
621✔
934
    default:
UNCOV
935
      result = false;
×
936
  }
937

938
  return result;
7,384✔
939
}
940

941
function _validArray(values, type) {
942
  if (!Array.isArray(values)) return false;
621!
943

944
  let arrayElementType;
945
  if (type === ParameterType.PARAMETER_BOOL_ARRAY) {
621✔
946
    arrayElementType = ParameterType.PARAMETER_BOOL;
2✔
947
  } else if (type === ParameterType.PARAMETER_BYTE_ARRAY) {
619✔
948
    arrayElementType = PARAMETER_BYTE;
154✔
949
  }
950
  if (type === ParameterType.PARAMETER_INTEGER_ARRAY) {
621✔
951
    arrayElementType = ParameterType.PARAMETER_INTEGER;
173✔
952
  }
953
  if (type === ParameterType.PARAMETER_DOUBLE_ARRAY) {
621✔
954
    arrayElementType = ParameterType.PARAMETER_DOUBLE;
146✔
955
  }
956
  if (type === ParameterType.PARAMETER_STRING_ARRAY) {
621✔
957
    arrayElementType = ParameterType.PARAMETER_STRING;
146✔
958
  }
959

960
  return values.reduce(
621✔
961
    (compatible, val) =>
962
      compatible &&
1,721✔
963
      !_isArrayParameterType(arrayElementType) &&
964
      validValue(val, arrayElementType),
965
    true
966
  );
967
}
968

969
function _isArrayParameterType(type) {
970
  return (
1,721✔
971
    type === ParameterType.PARAMETER_BOOL_ARRAY ||
8,605✔
972
    type === ParameterType.PARAMETER_BYTE_ARRAY ||
973
    type === ParameterType.PARAMETER_INTEGER_ARRAY ||
974
    type === ParameterType.PARAMETER_DOUBLE_ARRAY ||
975
    type === ParameterType.PARAMETER_STRING_ARRAY
976
  );
977
}
978

979
module.exports = {
26✔
980
  ParameterType,
981
  Parameter,
982
  ParameterDescriptor,
983
  PARAMETER_SEPARATOR,
984
  Range,
985
  FloatingPointRange,
986
  IntegerRange,
987
  DEFAULT_NUMERIC_RANGE_TOLERANCE,
988
  parameterTypeFromValue,
989
};
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