Skip to content

Commit d431ffa

Browse files
committed
Clamp numeric values to reguired validation constraints or allowed range
1 parent 2e088b2 commit d431ffa

File tree

9 files changed

+90
-29
lines changed

9 files changed

+90
-29
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@highmobility/auto-api-javascript",
3-
"version": "1.4.1",
3+
"version": "1.5.0",
44
"description": "Auto API for JavaScript - the parsing library for the Auto API vehicle data model",
55
"main": "lib/index.js",
66
"module": "es/index.js",

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export interface MeasurementType {
5454

5555
export type MeasurementTypes = MeasurementType[];
5656

57+
export interface NumericRange {
58+
min: number;
59+
max: number;
60+
}
61+
5762
export interface Property extends Omit<TypeDefinition, 'items'> {
5863
id: number;
5964
added?: number;

src/utils/encoding.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export function bytesWithSize(bytes: number[]) {
8181
return [...intToTwoBytes(bytes.length), ...bytes];
8282
}
8383

84+
export function clamp(value: number, min: number, max: number) {
85+
return Math.max(Math.min(value, max), min);
86+
}
87+
8488
export function decimalToHexArray(value: number | string, bytes = 1) {
8589
let hex = intToHex(typeof value === 'string' ? parseInt(value, 10) : value);
8690

@@ -91,6 +95,13 @@ export function decimalToHexArray(value: number | string, bytes = 1) {
9195
return hex.match(/.{1,2}/g)!.map((hexItem) => Number(`0x${hexItem}`));
9296
}
9397

98+
export function getNumericInputRange(size = 1, unsigned?: boolean) {
99+
return {
100+
min: unsigned ? 0 : -1 * Math.pow(2, size * 8 - 1),
101+
max: Math.pow(2, size * 8 - (unsigned ? 0 : 1)) - 1,
102+
};
103+
}
104+
94105
export function hexArrayToHex(hexArray: number[]) {
95106
return hexArray.reduce((memo, i) => memo + intToHex(i).padStart(2, '0'), '');
96107
}

src/values/DoubleValue.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ export class DoubleValue extends FloatValue {
1212
}
1313

1414
public decode(bytes: number[]) {
15-
this._value = ieee754DoubleToBase10(bytes, this.size);
16-
return this;
15+
return this.setValue(ieee754DoubleToBase10(bytes, this.size));
1716
}
1817

1918
public toString() {

src/values/FloatValue.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ import { TypeDefinition } from '../types';
22

33
import { FormatError } from '../core/Error';
44
import { NamedEntity } from '../core/NamedEntity';
5-
import { Value } from '../core/Value';
65

7-
import { base10ToIeee754, ieee754ToBase10, isNumber } from '../utils';
6+
import { base10ToIeee754, getNumericInputRange, ieee754ToBase10, isNumber } from '../utils';
87

9-
export class FloatValue extends Value<number> implements NamedEntity {
10-
public constructor(public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size'>>) {
11-
super();
8+
import { NumericValue } from './NumericValue';
9+
10+
export class FloatValue extends NumericValue implements NamedEntity {
11+
public constructor(
12+
public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size' | 'validation'>>,
13+
) {
14+
super(definition);
1215
}
1316

1417
public get name() {
1518
return this.definition.name;
1619
}
1720

21+
public get range() {
22+
return getNumericInputRange(this.size);
23+
}
24+
1825
public get size() {
1926
return this.definition.size || 4;
2027
}
@@ -24,8 +31,7 @@ export class FloatValue extends Value<number> implements NamedEntity {
2431
}
2532

2633
public decode(bytes: number[]) {
27-
this._value = ieee754ToBase10(bytes, this.size);
28-
return this;
34+
return this.setValue(ieee754ToBase10(bytes, this.size));
2935
}
3036

3137
public fromJSON(payload: unknown) {
@@ -38,11 +44,6 @@ export class FloatValue extends Value<number> implements NamedEntity {
3844
return this;
3945
}
4046

41-
public setValue(value: number) {
42-
this._value = value;
43-
return this;
44-
}
45-
4647
public toString() {
4748
return (this._value ?? NaN).toPrecision(7);
4849
}

src/values/IntegerValue.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ import { TypeDefinition } from '../types';
22

33
import { FormatError } from '../core/Error';
44
import { NamedEntity } from '../core/NamedEntity';
5-
import { Value } from '../core/Value';
65

7-
import { bytesToInt, decimalToHexArray, isInteger } from '../utils';
6+
import { bytesToInt, decimalToHexArray, getNumericInputRange, isInteger } from '../utils';
87

9-
export class IntegerValue extends Value<number> implements NamedEntity {
10-
public constructor(public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size'>>) {
11-
super();
8+
import { NumericValue } from './NumericValue';
9+
10+
export class IntegerValue extends NumericValue implements NamedEntity {
11+
public constructor(
12+
public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size' | 'validation'>>,
13+
) {
14+
super(definition);
1215
}
1316

1417
public get name() {
1518
return this.definition.name;
1619
}
1720

21+
public get range() {
22+
return getNumericInputRange(this.size);
23+
}
24+
1825
public get size() {
1926
return this.definition.size || 1;
2027
}
@@ -24,8 +31,7 @@ export class IntegerValue extends Value<number> implements NamedEntity {
2431
}
2532

2633
public decode(bytes: number[]) {
27-
this._value = bytesToInt(bytes);
28-
return this;
34+
return this.setValue(bytesToInt(bytes));
2935
}
3036

3137
public fromJSON(payload: unknown) {
@@ -38,11 +44,6 @@ export class IntegerValue extends Value<number> implements NamedEntity {
3844
return this;
3945
}
4046

41-
public setValue(value: number) {
42-
this._value = value;
43-
return this;
44-
}
45-
4647
public valueOf() {
4748
return this._value ?? null;
4849
}

src/values/NumericValue.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { NumericRange, TypeDefinition } from '../types';
2+
3+
import { Value } from '../core/Value';
4+
5+
import { clamp } from '../utils';
6+
7+
export abstract class NumericValue extends Value<number> {
8+
public constructor(public readonly definition: Readonly<Pick<TypeDefinition, 'validation'>>) {
9+
super();
10+
}
11+
12+
public abstract get range(): NumericRange;
13+
14+
public get validation(): NumericRange | undefined {
15+
const { validation } = this.definition;
16+
17+
if (validation) {
18+
const match = validation.match(/min:(.*)\|max:(.*)/);
19+
if (match) {
20+
return {
21+
min: Number(match[1]),
22+
max: Number(match[2]),
23+
};
24+
}
25+
}
26+
}
27+
28+
public clamp(value: number) {
29+
const { min, max } = this.validation || this.range;
30+
31+
return clamp(value, min, max);
32+
}
33+
34+
public setValue(value: number) {
35+
this._value = this.clamp(value);
36+
return this;
37+
}
38+
}

src/values/UintValue.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import { getNumericInputRange } from '../utils';
2+
13
import { IntegerValue } from './IntegerValue';
24

3-
export class UintValue extends IntegerValue {}
5+
export class UintValue extends IntegerValue {
6+
public get range() {
7+
return getNumericInputRange(this.size, true);
8+
}
9+
}

0 commit comments

Comments
 (0)