Skip to content

Commit

Permalink
Clamp numeric values to reguired validation constraints or allowed range
Browse files Browse the repository at this point in the history
  • Loading branch information
unematiii committed Mar 31, 2023
1 parent 2e088b2 commit d431ffa
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@highmobility/auto-api-javascript",
"version": "1.4.1",
"version": "1.5.0",
"description": "Auto API for JavaScript - the parsing library for the Auto API vehicle data model",
"main": "lib/index.js",
"module": "es/index.js",
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface MeasurementType {

export type MeasurementTypes = MeasurementType[];

export interface NumericRange {
min: number;
max: number;
}

export interface Property extends Omit<TypeDefinition, 'items'> {
id: number;
added?: number;
Expand Down
11 changes: 11 additions & 0 deletions src/utils/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export function bytesWithSize(bytes: number[]) {
return [...intToTwoBytes(bytes.length), ...bytes];
}

export function clamp(value: number, min: number, max: number) {
return Math.max(Math.min(value, max), min);
}

export function decimalToHexArray(value: number | string, bytes = 1) {
let hex = intToHex(typeof value === 'string' ? parseInt(value, 10) : value);

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

export function getNumericInputRange(size = 1, unsigned?: boolean) {
return {
min: unsigned ? 0 : -1 * Math.pow(2, size * 8 - 1),
max: Math.pow(2, size * 8 - (unsigned ? 0 : 1)) - 1,
};
}

export function hexArrayToHex(hexArray: number[]) {
return hexArray.reduce((memo, i) => memo + intToHex(i).padStart(2, '0'), '');
}
Expand Down
3 changes: 1 addition & 2 deletions src/values/DoubleValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ export class DoubleValue extends FloatValue {
}

public decode(bytes: number[]) {
this._value = ieee754DoubleToBase10(bytes, this.size);
return this;
return this.setValue(ieee754DoubleToBase10(bytes, this.size));
}

public toString() {
Expand Down
25 changes: 13 additions & 12 deletions src/values/FloatValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@ import { TypeDefinition } from '../types';

import { FormatError } from '../core/Error';
import { NamedEntity } from '../core/NamedEntity';
import { Value } from '../core/Value';

import { base10ToIeee754, ieee754ToBase10, isNumber } from '../utils';
import { base10ToIeee754, getNumericInputRange, ieee754ToBase10, isNumber } from '../utils';

export class FloatValue extends Value<number> implements NamedEntity {
public constructor(public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size'>>) {
super();
import { NumericValue } from './NumericValue';

export class FloatValue extends NumericValue implements NamedEntity {
public constructor(
public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size' | 'validation'>>,
) {
super(definition);
}

public get name() {
return this.definition.name;
}

public get range() {
return getNumericInputRange(this.size);
}

public get size() {
return this.definition.size || 4;
}
Expand All @@ -24,8 +31,7 @@ export class FloatValue extends Value<number> implements NamedEntity {
}

public decode(bytes: number[]) {
this._value = ieee754ToBase10(bytes, this.size);
return this;
return this.setValue(ieee754ToBase10(bytes, this.size));
}

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

public setValue(value: number) {
this._value = value;
return this;
}

public toString() {
return (this._value ?? NaN).toPrecision(7);
}
Expand Down
25 changes: 13 additions & 12 deletions src/values/IntegerValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@ import { TypeDefinition } from '../types';

import { FormatError } from '../core/Error';
import { NamedEntity } from '../core/NamedEntity';
import { Value } from '../core/Value';

import { bytesToInt, decimalToHexArray, isInteger } from '../utils';
import { bytesToInt, decimalToHexArray, getNumericInputRange, isInteger } from '../utils';

export class IntegerValue extends Value<number> implements NamedEntity {
public constructor(public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size'>>) {
super();
import { NumericValue } from './NumericValue';

export class IntegerValue extends NumericValue implements NamedEntity {
public constructor(
public readonly definition: Readonly<Pick<TypeDefinition, 'name' | 'size' | 'validation'>>,
) {
super(definition);
}

public get name() {
return this.definition.name;
}

public get range() {
return getNumericInputRange(this.size);
}

public get size() {
return this.definition.size || 1;
}
Expand All @@ -24,8 +31,7 @@ export class IntegerValue extends Value<number> implements NamedEntity {
}

public decode(bytes: number[]) {
this._value = bytesToInt(bytes);
return this;
return this.setValue(bytesToInt(bytes));
}

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

public setValue(value: number) {
this._value = value;
return this;
}

public valueOf() {
return this._value ?? null;
}
Expand Down
38 changes: 38 additions & 0 deletions src/values/NumericValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { NumericRange, TypeDefinition } from '../types';

import { Value } from '../core/Value';

import { clamp } from '../utils';

export abstract class NumericValue extends Value<number> {
public constructor(public readonly definition: Readonly<Pick<TypeDefinition, 'validation'>>) {
super();
}

public abstract get range(): NumericRange;

public get validation(): NumericRange | undefined {
const { validation } = this.definition;

if (validation) {
const match = validation.match(/min:(.*)\|max:(.*)/);
if (match) {
return {
min: Number(match[1]),
max: Number(match[2]),
};
}
}
}

public clamp(value: number) {
const { min, max } = this.validation || this.range;

return clamp(value, min, max);
}

public setValue(value: number) {
this._value = this.clamp(value);
return this;
}
}
8 changes: 7 additions & 1 deletion src/values/UintValue.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { getNumericInputRange } from '../utils';

import { IntegerValue } from './IntegerValue';

export class UintValue extends IntegerValue {}
export class UintValue extends IntegerValue {
public get range() {
return getNumericInputRange(this.size, true);
}
}

0 comments on commit d431ffa

Please sign in to comment.