Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Fix integer min/max range schema validation #8743

Merged
merged 3 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 10 additions & 19 deletions elements/lisk-validator/src/formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,21 @@
*
*/
import { address as cryptoAddress } from '@liskhq/lisk-cryptography';
import {
isHexString,
isBytes,
isNumberString,
isSInt64,
isUInt64,
isUInt32,
isSInt32,
isIP,
isIPV4,
isEncryptedPassphrase,
isSemVer,
} from './validation';
import { isHexString, isBytes, isIP, isIPV4, isEncryptedPassphrase, isSemVer } from './validation';
import { MAX_UINT32, MAX_UINT64 } from './constants';

export const hex = isHexString;
export const bytes = isBytes;

export const int64 = (data: string): boolean => isNumberString(data) && isSInt64(BigInt(data));

export const uint64 = (data: string): boolean => isNumberString(data) && isUInt64(BigInt(data));

export const uint32 = (data: string): boolean => isNumberString(data) && isUInt32(Number(data));
export const uint64 = {
type: 'number',
validate: (value: number) => Number.isInteger(value) && value >= 0 && value <= MAX_UINT64,
};

export const int32 = (data: string): boolean => isNumberString(data) && isSInt32(Number(data));
export const uint32 = {
type: 'number',
validate: (value: number) => Number.isInteger(value) && value >= 0 && value <= MAX_UINT32,
};

const camelCaseRegex = /^[a-z][0-9]*([A-Z][a-z]*[a-zA-Z0-9]*|[a-z][a-z]*[a-zA-Z0-9]*)?$/;

Expand Down
11 changes: 3 additions & 8 deletions elements/lisk-validator/src/lisk_validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*
*/

import Ajv, { SchemaObject, ValidateFunction } from 'ajv';
import Ajv, { Format, SchemaObject, ValidateFunction } from 'ajv';
import addDefaultFormats from 'ajv-formats';
import * as formats from './formats';
import { convertErrorsToLegacyFormat, LiskValidationError } from './errors';
Expand All @@ -32,7 +32,6 @@ export class LiskValidator {
strict: true,
strictSchema: true,
allErrors: true,
useDefaults: false,
// FIXME: Combination with lisk-codec schema, making true would throw error because
// Trace: Error: schema with key or id "/block/header"
addUsedSchema: false,
Expand All @@ -43,12 +42,8 @@ export class LiskValidator {

addDefaultFormats(this._validator);

for (const formatName of Object.keys(formats)) {
this._validator.addFormat(
formatName,
// eslint-disable-next-line import/namespace
formats[formatName as keyof typeof formats],
);
for (const [formatName, format] of Object.entries(formats)) {
this._validator.addFormat(formatName, format as Format);
}

this._validator.addKeyword({
Expand Down
162 changes: 119 additions & 43 deletions elements/lisk-validator/test/lisk_validator_formats.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,116 @@
*
*/
import { validator } from '../src';
import { MAX_SINT32, MAX_SINT64, MAX_UINT32, MAX_UINT64 } from '../src/constants';

describe('validator formats', () => {
const baseSchemaId = '/test/schema';
let baseSchema: Record<string, unknown>;
const baseSchema = {
$id: '/test/schema',
type: 'object',
};

beforeAll(() => {
baseSchema = {
$id: baseSchemaId,
type: 'object',
describe('uint32', () => {
const uint32IntegerSchema = {
...baseSchema,
properties: {
height: {
type: 'integer',
format: 'uint32',
},
},
};

it('should validate a correct uint32', () => {
expect(() => validator.validate(uint32IntegerSchema, { height: 8 })).not.toThrow();
});

it('should throw for a negative number', () => {
expect(() => validator.validate(uint32IntegerSchema, { height: -1 })).toThrow();
});

it('should throw when the number is too big', () => {
expect(() => validator.validate(uint32IntegerSchema, { height: MAX_UINT32 + 1 })).toThrow();
});
});

describe('hex', () => {
let schema: Record<string, unknown>;
beforeEach(() => {
schema = {
allOf: [
baseSchema,
{
properties: {
target: {
type: 'string',
format: 'hex',
},
},
},
],
};
describe('int32', () => {
const int32IntegerSchema = {
...baseSchema,
properties: {
height: {
type: 'integer',
format: 'int32',
},
},
};

it('should validate a correct int32', () => {
expect(() => validator.validate(int32IntegerSchema, { height: 8 })).not.toThrow();
});

it('should throw when the number is too big', () => {
expect(() => validator.validate(int32IntegerSchema, { height: MAX_SINT32 + 1 })).toThrow();
});
});

describe('uint64', () => {
const uint32IntegerSchema = {
...baseSchema,
properties: {
height: {
type: 'integer',
format: 'uint64',
},
},
};

it('should validate a correct uint64', () => {
expect(() => validator.validate(uint32IntegerSchema, { height: 8 })).not.toThrow();
});

it('should throw for a negative number', () => {
expect(() => validator.validate(uint32IntegerSchema, { height: -1 })).toThrow();
});

it('should throw when the number is too big', () => {
expect(() =>
validator.validate(uint32IntegerSchema, { height: MAX_UINT64 + BigInt(1) }),
).toThrow();
});
});

describe('int64', () => {
const int32IntegerSchema = {
...baseSchema,
properties: {
height: {
type: 'integer',
format: 'int64',
},
},
};

it('should validate a correct int64', () => {
expect(() => validator.validate(int32IntegerSchema, { height: 8 })).not.toThrow();
});

it('should throw when the number is too big', () => {
expect(() =>
validator.validate(int32IntegerSchema, { height: MAX_SINT64 + BigInt(1) }),
).toThrow();
});
});

describe('hex', () => {
const schema = {
...baseSchema,
properties: {
target: {
type: 'string',
format: 'hex',
},
},
};

it('should validate to true when valid hex string is provided', () => {
expect(() =>
Expand All @@ -53,31 +134,22 @@ describe('validator formats', () => {
const expectedError =
'Lisk validator found 1 error[s]:\nProperty \'.target\' must match format "hex"';

expect(() =>
validator.validate(schema, {
target: 'notValid?!hex-!!@',
}),
).toThrow(expectedError);
expect(() => validator.validate(schema, { target: 'notValid?!hex-!!@' })).toThrow(
expectedError,
);
});
});

describe('lisk32', () => {
let schema: Record<string, unknown>;
beforeEach(() => {
schema = {
allOf: [
baseSchema,
{
properties: {
target: {
type: 'string',
format: 'lisk32',
},
},
},
],
};
});
const schema = {
...baseSchema,
properties: {
target: {
type: 'string',
format: 'lisk32',
},
},
};

it('should validate to true when valid hex string is provided', () => {
expect(() =>
Expand Down Expand Up @@ -110,6 +182,7 @@ describe('validator formats', () => {

describe('path', () => {
const pathSchema = {
...baseSchema,
properties: {
rootPath: {
type: 'string',
Expand All @@ -135,6 +208,7 @@ describe('validator formats', () => {

describe('encryptedPassphrase', () => {
const encryptedPassphraseSchema = {
...baseSchema,
properties: {
encryptedPassphrase: {
type: 'string',
Expand Down Expand Up @@ -173,6 +247,7 @@ describe('validator formats', () => {

describe('camelCaseRegex', () => {
const camelCaseRegexSchema = {
...baseSchema,
properties: {
camelCaseRegex: {
type: 'string',
Expand Down Expand Up @@ -203,6 +278,7 @@ describe('validator formats', () => {

describe('version', () => {
const versionSchema = {
...baseSchema,
properties: {
version: {
type: 'string',
Expand Down
4 changes: 2 additions & 2 deletions framework/test/unit/modules/random/endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ describe('RandomModuleEndpoint', () => {

// Act & Assert
await expect(randomEndpoint.setHashOnionUsage(context)).rejects.toThrow(
"Lisk validator found 2 error[s]:\nProperty '.usedHashOnions.0.count' should be of type 'integer'\nProperty '.usedHashOnions.0.count' must match format \"uint32\"",
"Lisk validator found 1 error[s]:\nProperty '.usedHashOnions.0.count' should be of type 'integer'",
);
});

Expand All @@ -713,7 +713,7 @@ describe('RandomModuleEndpoint', () => {

// Act & Assert
await expect(randomEndpoint.setHashOnionUsage(context)).rejects.toThrow(
"Lisk validator found 2 error[s]:\nProperty '.usedHashOnions.0.height' should be of type 'integer'\nProperty '.usedHashOnions.0.height' must match format \"uint32\"",
"Lisk validator found 1 error[s]:\nProperty '.usedHashOnions.0.height' should be of type 'integer'",
);
});
});
Expand Down