rclnodejs 1.6.0 → 1.7.0

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/lib/parameter.js CHANGED
@@ -20,6 +20,12 @@
20
20
  'use strict';
21
21
 
22
22
  const { isClose } = require('./utils.js');
23
+ const {
24
+ TypeValidationError,
25
+ RangeValidationError,
26
+ ParameterError,
27
+ ParameterTypeError,
28
+ } = require('./errors.js');
23
29
 
24
30
  /**
25
31
  * The plus/minus tolerance for determining number equivalence.
@@ -184,17 +190,25 @@ class Parameter {
184
190
  typeof this.name !== 'string' ||
185
191
  this.name.trim().length === 0
186
192
  ) {
187
- throw new TypeError('Invalid name');
193
+ throw new TypeValidationError('name', this.name, 'non-empty string', {
194
+ entityType: 'parameter',
195
+ });
188
196
  }
189
197
 
190
198
  // validate type
191
199
  if (!validType(this.type)) {
192
- throw new TypeError('Invalid type');
200
+ throw new ParameterError('Invalid parameter type', this.name, {
201
+ details: { providedType: this.type },
202
+ });
193
203
  }
194
204
 
195
205
  // validate value
196
206
  if (!validValue(this.value, this.type)) {
197
- throw new TypeError('Incompatible value.');
207
+ throw new ParameterTypeError(this.name, this.type, typeof this.value, {
208
+ details: {
209
+ providedValue: this.value,
210
+ },
211
+ });
198
212
  }
199
213
 
200
214
  this._dirty = false;
@@ -383,10 +397,23 @@ class ParameterDescriptor {
383
397
  return;
384
398
  }
385
399
  if (!(range instanceof Range)) {
386
- throw TypeError('Expected instance of Range.');
400
+ throw new TypeValidationError('range', range, 'Range', {
401
+ entityType: 'parameter descriptor',
402
+ parameterName: this.name,
403
+ });
387
404
  }
388
405
  if (!range.isValidType(this.type)) {
389
- throw TypeError('Incompatible Range');
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
+ );
390
417
  }
391
418
 
392
419
  this._range = range;
@@ -405,22 +432,40 @@ class ParameterDescriptor {
405
432
  typeof this.name !== 'string' ||
406
433
  this.name.trim().length === 0
407
434
  ) {
408
- throw new TypeError('Invalid name');
435
+ throw new TypeValidationError('name', this.name, 'non-empty string', {
436
+ entityType: 'parameter descriptor',
437
+ });
409
438
  }
410
439
 
411
440
  // validate type
412
441
  if (!validType(this.type)) {
413
- throw new TypeError('Invalid type');
442
+ throw new ParameterError('Invalid parameter type', this.name, {
443
+ entityType: 'parameter descriptor',
444
+ details: { providedType: this.type },
445
+ });
414
446
  }
415
447
 
416
448
  // validate description
417
449
  if (this.description && typeof this.description !== 'string') {
418
- throw new TypeError('Invalid description');
450
+ throw new TypeValidationError('description', this.description, 'string', {
451
+ entityType: 'parameter descriptor',
452
+ parameterName: this.name,
453
+ });
419
454
  }
420
455
 
421
456
  // validate rangeConstraint
422
457
  if (this.hasRange() && !this.range.isValidType(this.type)) {
423
- throw new TypeError('Incompatible Range');
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
+ );
424
469
  }
425
470
  }
426
471
 
@@ -433,27 +478,66 @@ class ParameterDescriptor {
433
478
  */
434
479
  validateParameter(parameter) {
435
480
  if (!parameter) {
436
- throw new TypeError('Parameter is undefined');
481
+ throw new TypeValidationError('parameter', parameter, 'Parameter', {
482
+ entityType: 'parameter descriptor',
483
+ parameterName: this.name,
484
+ });
437
485
  }
438
486
 
439
487
  // ensure parameter is valid
440
488
  try {
441
489
  parameter.validate();
442
- } catch {
443
- throw new TypeError('Parameter is invalid');
490
+ } catch (err) {
491
+ throw new ParameterError('Parameter is invalid', parameter.name, {
492
+ cause: err,
493
+ details: { validationError: err.message },
494
+ });
444
495
  }
445
496
 
446
497
  // ensure this descriptor is valid
447
498
  try {
448
499
  this.validate();
449
- } catch {
450
- throw new Error('Descriptor is invalid.');
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
+ });
451
506
  }
452
507
 
453
- if (this.name !== parameter.name) throw new Error('Name mismatch');
454
- if (this.type !== parameter.type) throw new Error('Type mismatch');
508
+ if (this.name !== parameter.name) {
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) {
517
+ throw new ParameterTypeError(this.name, this.type, parameter.type, {
518
+ details: {
519
+ expectedType: this.type,
520
+ actualType: parameter.type,
521
+ },
522
+ });
523
+ }
455
524
  if (this.hasRange() && !this.range.inRange(parameter.value)) {
456
- throw new RangeError('Parameter value is not in descriptor range');
525
+ throw new RangeValidationError(
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
+ );
457
541
  }
458
542
  }
459
543
 
@@ -550,7 +634,9 @@ class Range {
550
634
  true
551
635
  );
552
636
  } else if (typeof value !== 'number' && typeof value !== 'bigint') {
553
- throw new TypeError('Value must be a number or bigint');
637
+ throw new TypeValidationError('value', value, 'number or bigint', {
638
+ entityType: 'range',
639
+ });
554
640
  }
555
641
 
556
642
  return true;
@@ -693,30 +779,80 @@ class IntegerRange extends Range {
693
779
  * @param {any} value - The value to infer it's ParameterType
694
780
  * @returns {ParameterType} - The ParameterType that best scribes the value.
695
781
  */
696
- // eslint-disable-next-line no-unused-vars
697
782
  function parameterTypeFromValue(value) {
698
- if (!value) return ParameterType.PARAMETER_NOT_SET;
699
- if (typeof value === 'boolean') return ParameterType.PARAMETER_BOOL;
700
- if (typeof value === 'string') return ParameterType.PARAMETER_STRING;
701
- if (typeof value === 'number') return ParameterType.PARAMETER_DOUBLE;
702
- if (Array.isArray(value)) {
783
+ if (value === null || value === undefined) {
784
+ return ParameterType.PARAMETER_NOT_SET;
785
+ }
786
+
787
+ if (typeof value === 'boolean') {
788
+ return ParameterType.PARAMETER_BOOL;
789
+ }
790
+
791
+ if (typeof value === 'string') {
792
+ return ParameterType.PARAMETER_STRING;
793
+ }
794
+
795
+ if (typeof value === 'bigint') {
796
+ return ParameterType.PARAMETER_INTEGER;
797
+ }
798
+
799
+ if (typeof value === 'number') {
800
+ // Distinguish between integer and double
801
+ return Number.isInteger(value)
802
+ ? ParameterType.PARAMETER_INTEGER
803
+ : ParameterType.PARAMETER_DOUBLE;
804
+ }
805
+
806
+ // Handle TypedArrays
807
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
808
+ if (value instanceof Uint8Array) {
809
+ return ParameterType.PARAMETER_BYTE_ARRAY;
810
+ }
811
+ // For other typed arrays, infer from first element
703
812
  if (value.length > 0) {
704
- const elementType = parameterTypeFromValue(value[0]);
705
- switch (elementType) {
706
- case ParameterType.PARAMETER_BOOL:
707
- return ParameterType.PARAMETER_BOOL_ARRAY;
708
- case ParameterType.PARAMETER_DOUBLE:
709
- return ParameterType.PARAMETER_DOUBLE_ARRAY;
710
- case ParameterType.PARAMETER_STRING:
711
- return ParameterType.PARAMETER_STRING_ARRAY;
813
+ const firstType = parameterTypeFromValue(value[0]);
814
+ if (firstType === ParameterType.PARAMETER_INTEGER) {
815
+ return ParameterType.PARAMETER_INTEGER_ARRAY;
712
816
  }
817
+ return ParameterType.PARAMETER_DOUBLE_ARRAY;
713
818
  }
714
-
715
819
  return ParameterType.PARAMETER_NOT_SET;
716
820
  }
717
821
 
718
- // otherwise unrecognized value
719
- throw new TypeError('Unrecognized parameter type.');
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
+ });
720
856
  }
721
857
 
722
858
  /**
@@ -826,4 +962,5 @@ module.exports = {
826
962
  FloatingPointRange,
827
963
  IntegerRange,
828
964
  DEFAULT_NUMERIC_RANGE_TOLERANCE,
965
+ parameterTypeFromValue,
829
966
  };