From 751e8c52d3295941bdc6bd1515db76a3fd388795 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Fri, 9 Jun 2023 15:42:24 +0300 Subject: [PATCH 01/17] change internal validation to jsonschema lib --- packages/web3-validator/package.json | 2 +- packages/web3-validator/src/types.ts | 5 +- packages/web3-validator/src/validator.ts | 160 ++++-------------- .../test/unit/validator.test.ts | 26 +-- .../test/unit/web3_validator.test.ts | 6 +- yarn.lock | 43 +---- 6 files changed, 43 insertions(+), 199 deletions(-) diff --git a/packages/web3-validator/package.json b/packages/web3-validator/package.json index 3501b94c135..a11e63d2163 100644 --- a/packages/web3-validator/package.json +++ b/packages/web3-validator/package.json @@ -46,7 +46,7 @@ }, "dependencies": { "ethereum-cryptography": "^2.0.0", - "is-my-json-valid": "^2.20.6", + "jsonschema": "^1.4.1", "util": "^0.12.5", "web3-errors": "^1.0.0", "web3-types": "^1.0.0" diff --git a/packages/web3-validator/src/types.ts b/packages/web3-validator/src/types.ts index 5610dd72b24..a0ecc7b1f7d 100644 --- a/packages/web3-validator/src/types.ts +++ b/packages/web3-validator/src/types.ts @@ -16,7 +16,7 @@ along with web3.js. If not, see . */ import { AbiParameter } from 'web3-types'; -import { ValidationError } from 'is-my-json-valid'; +import { ValidationError } from 'jsonschema'; export type ValidInputTypes = Uint8Array | bigint | string | number | boolean; export type EthBaseTypes = 'bool' | 'bytes' | 'string' | 'uint' | 'int' | 'address' | 'tuple'; @@ -145,8 +145,5 @@ export interface Validate { (value: Json): boolean; errors?: ValidationError[]; } -export type RawValidationError = ValidationError & { - schemaPath: string[]; -}; export type JsonSchema = Schema; diff --git a/packages/web3-validator/src/validator.ts b/packages/web3-validator/src/validator.ts index f57cd639480..842e245f5f0 100644 --- a/packages/web3-validator/src/validator.ts +++ b/packages/web3-validator/src/validator.ts @@ -16,53 +16,31 @@ along with web3.js. If not, see . */ import { Web3ValidationErrorObject } from 'web3-types'; -import { toHex, utf8ToBytes } from 'ethereum-cryptography/utils.js'; -import { blake2b } from 'ethereum-cryptography/blake2b.js'; -import validator from 'is-my-json-valid'; +import { Validator as JsonSchemaValidator, ValidationError } from 'jsonschema'; import formats from './formats.js'; import { Web3ValidatorError } from './errors.js'; -import { Validate, Json, Schema, RawValidationError } from './types.js'; +import { Json, Schema } from './types.js'; export class Validator { + private readonly internalValidator: JsonSchemaValidator; + private constructor() { + JsonSchemaValidator.prototype.customFormats = formats; + this.internalValidator = new JsonSchemaValidator(); + } // eslint-disable-next-line no-use-before-define private static validatorInstance?: Validator; // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function - private constructor() {} public static factory(): Validator { if (!Validator.validatorInstance) { Validator.validatorInstance = new Validator(); } return Validator.validatorInstance; } - private readonly _schemas: Map = new Map(); - public getSchema(key: string) { - return this._schemas.get(key); - } - - public addSchema(key: string, schema: Schema) { - this._schemas.set(key, this.createValidator(schema)); - } - - // eslint-disable-next-line class-methods-use-this - private createValidator(schema: Schema): Validate { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - // @ts-expect-error validator params correction - return validator(schema, { - formats, - greedy: true, - verbose: true, - additionalProperties: false, - }) as Validate; - } public validate(schema: Schema, data: Json, options?: { silent?: boolean }) { - const localValidate = this.getOrCreateValidator(schema); - if (!localValidate(data)) { - const errors = this.convertErrors( - localValidate.errors as RawValidationError[], - schema, - data, - ); + const validationResult = this.internalValidator.validate(data, schema); + if (!validationResult.valid) { + const errors = this.convertErrors(validationResult.errors); if (errors) { if (options?.silent) { return errors; @@ -72,129 +50,59 @@ export class Validator { } return undefined; } + // eslint-disable-next-line class-methods-use-this private convertErrors( - errors: RawValidationError[] | undefined, - schema: Schema, - data: Json, + errors: ValidationError[] | undefined, ): Web3ValidationErrorObject[] | undefined { if (errors && Array.isArray(errors) && errors.length > 0) { - return errors.map((error: RawValidationError) => { + return errors.map((error: ValidationError) => { let message; let keyword; let params; let schemaPath; - schemaPath = Array.isArray(error.schemaPath) - ? error.schemaPath.slice(1).join('/') - : ''; + schemaPath = error.path.join('/'); - const { field } = error; - const _instancePath = - schemaPath || - // eslint-disable-next-line no-useless-escape - (field?.length >= 4 ? `${field.slice(4).replace(/\"|\[|\]/g, '')}` : '/'); - - const instancePath = _instancePath ? `/${_instancePath}` : ''; - if (error?.message === 'has less items than allowed') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const schemaData = this.getObjectValueByPath(schema, schemaPath); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (schemaData.minItems) { + const field = error.property; + const instancePath = error.path.join('/'); + if (error?.message.startsWith('does not meet minimum length of')) { + if (error.argument) { keyword = 'minItems'; - schemaPath = `${schemaPath}/minItems`; + schemaPath = `${instancePath}/minItems`; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - params = { limit: schemaData.minItems }; + params = { limit: error.argument }; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions - message = `must NOT have fewer than ${schemaData.minItems} items`; + message = `must NOT have fewer than ${error.argument} items`; } - } else if (error?.message === 'has more items than allowed') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const schemaData = this.getObjectValueByPath(schema, schemaPath); + } else if (error?.message.startsWith('does not meet maximum length of')) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (schemaData.maxItems) { + if (error.argument) { keyword = 'maxItems'; - schemaPath = `${schemaPath}/maxItems`; + schemaPath = `${instancePath}/maxItems`; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - params = { limit: schemaData.maxItems }; + params = { limit: error.argument }; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions - message = `must NOT have more than ${schemaData.maxItems} items`; + message = `must NOT have more than ${error.argument} items`; } } else if ( - error?.message.startsWith('must be') && + error?.message.startsWith('does not conform to the') && error?.message.endsWith('format') ) { - const formatName = error?.message.split(' ')[2]; + const formatName = error?.message.split(' ')[5]; if (formatName) { - message = `must pass "${formatName}" validation`; + message = `must pass ${formatName} validation`; } } - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const dataValue = this.getObjectValueByPath(data as object, instancePath); return { - keyword: keyword ?? error.field, - instancePath, - schemaPath: `#${schemaPath}`, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - params: params ?? { value: dataValue }, + keyword: keyword ?? field.replace('instance', 'data'), + instancePath: instancePath ? `/${instancePath}` : '', + schemaPath: schemaPath ? `#${schemaPath}` : '#', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + params: params ?? { value: error.instance }, message: message ?? error.message, } as Web3ValidationErrorObject; }); } return undefined; } - - public getOrCreateValidator(schema: Schema): Validate { - const key = Validator.getKey(schema); - let _validator = this.getSchema(key); - if (!_validator) { - this.addSchema(key, schema); - _validator = this.getSchema(key); - } - return _validator!; - } - - public static getKey(schema: Schema) { - return toHex(blake2b(utf8ToBytes(JSON.stringify(schema)))); - } - private getObjectValueByPath(obj: object, pointer: string, objpath?: object[]) { - try { - if (typeof obj !== 'object') throw new Error('Invalid input object'); - if (typeof pointer !== 'string') throw new Error('Invalid JSON pointer'); - const parts = pointer.split('/'); - if (!['', '#'].includes(parts.shift() as string)) { - throw new Error('Invalid JSON pointer'); - } - if (parts.length === 0) return obj; - - let curr: any = obj; - for (const part of parts) { - if (typeof part !== 'string') throw new Error('Invalid JSON pointer'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument - if (objpath) objpath.push(curr); // does not include target itself, but includes head - const prop = this.untilde(part); - if (typeof curr !== 'object') return undefined; - if (!Object.prototype.hasOwnProperty.call(curr, prop)) return undefined; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - curr = curr[prop]; - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return curr; - } catch (e) { - return ''; - } - } - // eslint-disable-next-line class-methods-use-this - private untilde(string: string) { - if (!string.includes('~')) return string; - return string.replace(/~[01]/g, match => { - switch (match) { - case '~1': - return '/'; - case '~0': - return '~'; - default: - throw new Error('Unreachable'); - } - }); - } } diff --git a/packages/web3-validator/test/unit/validator.test.ts b/packages/web3-validator/test/unit/validator.test.ts index 7bf08497161..e8afc8045d0 100644 --- a/packages/web3-validator/test/unit/validator.test.ts +++ b/packages/web3-validator/test/unit/validator.test.ts @@ -42,20 +42,8 @@ describe('instance of validator', () => { it('instance', () => { expect(validator).toBeDefined(); expect(validator.validate).toBeDefined(); - expect(validator.addSchema).toBeDefined(); - expect(validator.getOrCreateValidator).toBeDefined(); - expect(validator.getSchema).toBeDefined(); - }); - it('add/get schema', () => { - const schema = { - type: 'array', - items: { - format: 'uint', - }, - }; - validator.addSchema('k', schema); - expect(typeof validator.getSchema('k')).toBe('function'); }); + it('convertErrors', () => { const schema = { type: 'array', @@ -66,18 +54,6 @@ describe('instance of validator', () => { // @ts-expect-error-next-line expect(validator.convertErrors(undefined, schema, [])).toBeUndefined(); }); - it('getObjectValueByPath', () => { - // @ts-expect-error-next-line - expect(validator.getObjectValueByPath({}, '$')).toBe(''); - }); - it('untilde', () => { - // @ts-expect-error-next-line - expect(validator.untilde('~1')).toBe('/'); - // @ts-expect-error-next-line - expect(validator.untilde('~0')).toBe('~'); - // @ts-expect-error-next-line - expect(validator.untilde('123')).toBe('123'); - }); it('formats exists', () => { for (const f of formatNames) { expect(typeof formats[f]).toBe('function'); diff --git a/packages/web3-validator/test/unit/web3_validator.test.ts b/packages/web3-validator/test/unit/web3_validator.test.ts index e336ad03965..29423e0d71e 100644 --- a/packages/web3-validator/test/unit/web3_validator.test.ts +++ b/packages/web3-validator/test/unit/web3_validator.test.ts @@ -64,12 +64,10 @@ describe('web3-validator', () => { expect(validator.validate(['uint'], [-1], { silent: true })).toEqual([ { instancePath: '/0', - keyword: 'data["0"]', - // keyword: 'eth', + keyword: 'data[0]', message: 'must pass "uint" validation', params: { value: -1 }, - schemaPath: '#', - // schemaPath: '#/items/0/eth', + schemaPath: '#0', }, ]); }); diff --git a/yarn.lock b/yarn.lock index 4dca974e8bc..46e30d69c54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5722,20 +5722,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generate-function@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ== - dependencies: - is-property "^1.0.0" - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -6744,22 +6730,6 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-my-ip-valid@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz#f7220d1146257c98672e6fba097a9f3f2d348442" - integrity sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg== - -is-my-json-valid@^2.20.6: - version "2.20.6" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz#a9d89e56a36493c77bda1440d69ae0dc46a08387" - integrity sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^5.0.0" - xtend "^4.0.0" - is-nan@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" @@ -6848,11 +6818,6 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-property@^1.0.0, is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -7659,10 +7624,10 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonpointer@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" - integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +jsonschema@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" + integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== jsprim@^1.2.2: version "1.4.2" From cbaf6842fc7698c5e147665e7cb141a17417aa1c Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Fri, 7 Jul 2023 17:06:34 -0400 Subject: [PATCH 02/17] implement validation with zod --- .../src/errors/transaction_errors.ts | 3 +- packages/web3-errors/test/unit/errors.test.ts | 2 +- .../fixtures/contracts/SimpleOverloaded.sol | 15 +- .../integration/block/rpc.getBlock.test.ts | 24 +-- .../web3-utils/test/fixtures/converters.ts | 44 +++--- .../test/fixtures/string_manipulation.ts | 9 +- packages/web3-validator/package.json | 4 +- packages/web3-validator/src/errors.ts | 20 +-- packages/web3-validator/src/formats.ts | 4 +- packages/web3-validator/src/types.ts | 4 +- packages/web3-validator/src/validator.ts | 146 ++++++++++++------ packages/web3-validator/src/web3_validator.ts | 3 - .../web3-validator/test/fixtures/errors.ts | 19 --- .../web3-validator/test/unit/error.test.ts | 30 +--- .../test/unit/web3_validator.test.ts | 8 +- yarn.lock | 10 +- 16 files changed, 159 insertions(+), 186 deletions(-) diff --git a/packages/web3-errors/src/errors/transaction_errors.ts b/packages/web3-errors/src/errors/transaction_errors.ts index b19341111eb..c74aebee767 100644 --- a/packages/web3-errors/src/errors/transaction_errors.ts +++ b/packages/web3-errors/src/errors/transaction_errors.ts @@ -557,10 +557,11 @@ export class InvalidPropertiesForTransactionTypeError extends BaseWeb3Error { const invalidPropertyNames: string[] = []; validationError.forEach(error => invalidPropertyNames.push( + error.keyword, // These errors are erroneously reported, error // has type Web3ValidationErrorObject, but eslint doesn't recognize it // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - (error.keyword.match(/data.(.+)/) as string[])[1], + // (error.keyword.match(/data.(.+)/) as string[])[1], ), ); super( diff --git a/packages/web3-errors/test/unit/errors.test.ts b/packages/web3-errors/test/unit/errors.test.ts index a6bf29bdb82..91772390af7 100644 --- a/packages/web3-errors/test/unit/errors.test.ts +++ b/packages/web3-errors/test/unit/errors.test.ts @@ -198,7 +198,7 @@ describe('errors', () => { new transactionErrors.InvalidPropertiesForTransactionTypeError( [ { - keyword: 'data.property', + keyword: 'property', instancePath: '', schemaPath: '', params: {}, diff --git a/packages/web3-eth-contract/test/fixtures/contracts/SimpleOverloaded.sol b/packages/web3-eth-contract/test/fixtures/contracts/SimpleOverloaded.sol index 515719211ae..3aa74fa05d8 100644 --- a/packages/web3-eth-contract/test/fixtures/contracts/SimpleOverloaded.sol +++ b/packages/web3-eth-contract/test/fixtures/contracts/SimpleOverloaded.sol @@ -16,10 +16,11 @@ contract SimpleOverload { return secret + numToAdd; } - function getSecret( - uint256 numToAdd, - string calldata _someString - ) public view returns (uint256, string memory) { + function getSecret(uint256 numToAdd, string calldata _someString) + public + view + returns (uint256, string memory) + { return (secret + numToAdd, string.concat(someString, _someString)); } @@ -37,14 +38,14 @@ contract SimpleOverload { } function multicall(bytes[] calldata datas) external { - for (uint i = 0; i < datas.length; i++) { + for (uint256 i = 0; i < datas.length; i++) { address(this).call(datas[i]); } } - function multicall(uint deadline, bytes[] calldata datas) external { + function multicall(uint256 deadline, bytes[] calldata datas) external { require(block.timestamp <= deadline); - for (uint i = 0; i < datas.length; i++) { + for (uint256 i = 0; i < datas.length; i++) { address(this).call(datas[i]); } } diff --git a/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts b/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts index e967390ec4a..63f0f3c402f 100644 --- a/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts +++ b/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts @@ -131,29 +131,7 @@ describe('rpc with block', () => { b.totalDifficulty = '0x0'; } - // just fix tests for oneOf validation - // @TODO: when use schemasafe remove this fix - const schema = JSON.parse(JSON.stringify(blockSchema)); - if ( - b.transactions && - Array.isArray(b.transactions) && - typeof b.transactions[0] === 'object' - ) { - // eslint-disable-next-line prefer-destructuring - schema.properties.transactions = schema.properties.transactions.oneOf[0]; - // @ts-expect-error add leading zeros remove when fixes https://github.com/web3/web3.js/issues/6060 - b.transactions[0].s = `0x${`000000000000000${b?.transactions[0]?.s.slice(2)}`.slice( - -64, - )}`; - // @ts-expect-error add leading zeros remove when fixes https://github.com/web3/web3.js/issues/6060 - b.transactions[0].r = `0x${`000000000000000${b?.transactions[0]?.r.slice(2)}`.slice( - -64, - )}`; - } else { - // eslint-disable-next-line prefer-destructuring - schema.properties.transactions = schema.properties.transactions.oneOf[1]; - } - expect(validator.validateJSONSchema(schema, b)).toBeUndefined(); + expect(validator.validateJSONSchema(blockSchema, b)).toBeUndefined(); if (hydrated && b.transactions?.length > 0) { // eslint-disable-next-line jest/no-conditional-expect expect(b.transactions).toBeInstanceOf(Array); diff --git a/packages/web3-utils/test/fixtures/converters.ts b/packages/web3-utils/test/fixtures/converters.ts index ed3f7bb9274..42d992ec34a 100644 --- a/packages/web3-utils/test/fixtures/converters.ts +++ b/packages/web3-utils/test/fixtures/converters.ts @@ -27,21 +27,21 @@ export const bytesToHexValidData: [Bytes, HexString][] = [ ]; export const bytesToHexInvalidData: [any, string][] = [ - [[9.5, 12.9], 'value "9.5,12.9" at "/0" must pass "bytes" validation'], - [[-72, 12], 'value "-72,12" at "/0" must pass "bytes" validation'], - [[567, 10098], 'value "567,10098" at "/0" must pass "bytes" validation'], - [[786, 12, 34, -2, 3], 'value "786,12,34,-2,3" at "/0" must pass "bytes" validation'], + [[9.5, 12.9], 'value "[9.5,12.9]" at "/0" must pass "bytes" validation'], + [[-72, 12], 'value "[-72,12]" at "/0" must pass "bytes" validation'], + [[567, 10098], 'value "[567,10098]" at "/0" must pass "bytes" validation'], + [[786, 12, 34, -2, 3], 'value "[786,12,34,-2,3]" at "/0" must pass "bytes" validation'], ['0x0c1g', 'value "0x0c1g" at "/0" must pass "bytes" validation'], ['0c1g', 'value "0c1g" at "/0" must pass "bytes" validation'], ['0x123', 'value "0x123" at "/0" must pass "bytes" validation'], ['data', 'value "data" at "/0" must pass "bytes" validation'], [12, 'value "12" at "/0" must pass "bytes" validation'], - [['string'], 'value "string" at "/0" must pass "bytes" validation'], + [['string'], 'value "["string"]" at "/0" must pass "bytes" validation'], // Using "null" value intentionally for validation // eslint-disable-next-line no-null/no-null - [null, 'value at "/0" must pass "bytes" validation'], + [null, 'value "null" at "/0" must pass "bytes" validation'], [undefined, 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [{}, 'value "[object Object]" at "/0" must pass "bytes" validation'], + [{}, 'value "{}" at "/0" must pass "bytes" validation'], ['1', 'value "1" at "/0" must pass "bytes" validation'], ['0', 'value "0" at "/0" must pass "bytes" validation'], ]; @@ -56,21 +56,21 @@ export const hexToBytesValidData: [HexString, Uint8Array][] = [ ]; export const hexToBytesInvalidData: [any, string][] = [ - [[9.5, 12.9], 'value "9.5,12.9" at "/0" must pass "bytes" validation'], - [[-72, 12], 'value "-72,12" at "/0" must pass "bytes" validation'], - [[567, 10098], 'value "567,10098" at "/0" must pass "bytes" validation'], - [[786, 12, 34, -2, 3], 'value "786,12,34,-2,3" at "/0" must pass "bytes" validation'], + [[9.5, 12.9], 'value "[9.5,12.9]" at "/0" must pass "bytes" validation'], + [[-72, 12], 'value "[-72,12]" at "/0" must pass "bytes" validation'], + [[567, 10098], 'value "[567,10098]" at "/0" must pass "bytes" validation'], + [[786, 12, 34, -2, 3], 'value "[786,12,34,-2,3]" at "/0" must pass "bytes" validation'], ['0x0c1g', 'value "0x0c1g" at "/0" must pass "bytes" validation'], ['0c1g', 'value "0x0c1g" at "/0" must pass "bytes" validation'], ['0x123', 'value "0x123" at "/0" must pass "bytes" validation'], ['data', 'value "0xdata" at "/0" must pass "bytes" validation'], [12, 'value "12" at "/0" must pass "bytes" validation'], - [['string'], 'value "string" at "/0" must pass "bytes" validation'], + [['string'], 'value "["string"]" at "/0" must pass "bytes" validation'], // Using "null" value intentionally for validation // eslint-disable-next-line no-null/no-null - [null, 'Web3 validator found 1 error[s]:\nvalue at "/0" must pass "bytes" validation'], + [null, 'Web3 validator found 1 error[s]:\nvalue "null" at "/0" must pass "bytes" validation'], [undefined, 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [{}, 'value "[object Object]" at "/0" must pass "bytes" validation'], + [{}, 'value "{}" at "/0" must pass "bytes" validation'], ]; export const numberToHexValidData: [Numbers, HexString][] = [ @@ -109,9 +109,9 @@ export const numberToHexInvalidData: [any, string][] = [ ['122g', 'value "122g" at "/0" must pass "int" validation'], // Using "null" value intentionally for validation // eslint-disable-next-line no-null/no-null - [null, 'value at "/0" must pass "int" validation'], + [null, 'value "null" at "/0" must pass "int" validation'], [undefined, 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [{}, 'value "[object Object]" at "/0" must pass "int" validation'], + [{}, 'value "{}" at "/0" must pass "int" validation'], ]; export const hexToNumberValidData: [HexString, Numbers][] = [ @@ -154,9 +154,9 @@ export const utf8ToHexInvalidData: [any, string][] = [ [BigInt(12), 'value "12" at "/0" must pass "string" validation'], // Using "null" value intentionally for validation // eslint-disable-next-line no-null/no-null - [null, 'value at "/0" must pass "string" validation'], + [null, 'value "null" at "/0" must pass "string" validation'], [undefined, 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [{}, 'value "[object Object]" at "/0" must pass "string" validation'], + [{}, 'value "{}" at "/0" must pass "string" validation'], [true, 'value "true" at "/0" must pass "string" validation'], [false, 'value "false" at "/0" must pass "string" validation'], ]; @@ -185,9 +185,9 @@ export const hexToUtf8InvalidData: [any, string][] = [ ], // Using "null" value intentionally for validation // eslint-disable-next-line no-null/no-null - [null, 'value at "/0" must pass "bytes" validation'], + [null, 'value "null" at "/0" must pass "bytes" validation'], [undefined, 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [{}, 'value "[object Object]" at "/0" must pass "bytes" validation'], + [{}, 'value "{}" at "/0" must pass "bytes" validation'], [true, 'value "true" at "/0" must pass "bytes" validation'], ]; @@ -302,9 +302,9 @@ export const fromWeiInvalidData: [[any, any], string][] = [ export const toWeiInvalidData: [[any, any], string][] = [ // Using "null" value intentionally for validation // eslint-disable-next-line no-null/no-null - [[null, 'kwei'], 'value at "/0" must pass "number" validation'], + [[null, 'kwei'], 'value "null" at "/0" must pass "number" validation'], [[undefined, 'kwei'], 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [[{}, 'kwei'], 'value "[object Object]" at "/0" must pass "number" validation'], + [[{}, 'kwei'], 'value "{}" at "/0" must pass "number" validation'], [['data', 'kwei'], 'value "data" at "/0" must pass "number" validation'], [['1234', 'uwei'], 'Invalid value given "uwei". Error: invalid unit.'], ]; diff --git a/packages/web3-utils/test/fixtures/string_manipulation.ts b/packages/web3-utils/test/fixtures/string_manipulation.ts index e2679530a39..be5b298c803 100644 --- a/packages/web3-utils/test/fixtures/string_manipulation.ts +++ b/packages/web3-utils/test/fixtures/string_manipulation.ts @@ -34,10 +34,13 @@ export const padLeftData: [[Numbers, number, string], HexString][] = [ export const padInvalidData: [[any, number, string], string][] = [ [[9.5, 64, 'f'], 'value "9.5" at "/0" must pass "int" validation'], // Using "null" value intentionally for validation - // eslint-disable-next-line no-null/no-null - [[null, 8, '0'], 'Web3 validator found 1 error[s]:\nvalue at "/0" must pass "int" validation'], + [ + // eslint-disable-next-line no-null/no-null + [null, 8, '0'], + 'Web3 validator found 1 error[s]:\nvalue "null" at "/0" must pass "int" validation', + ], [[undefined, 8, '0'], 'Web3 validator found 1 error[s]:\nvalue at "/0" is required'], - [[{}, 3, 'f'], 'value "[object Object]" at "/0" must pass "int" validation'], + [[{}, 3, 'f'], 'value "{}" at "/0" must pass "int" validation'], ]; export const padRightData: [[Numbers, number, string], HexString][] = [ diff --git a/packages/web3-validator/package.json b/packages/web3-validator/package.json index a11e63d2163..3c3cca80e7a 100644 --- a/packages/web3-validator/package.json +++ b/packages/web3-validator/package.json @@ -46,10 +46,10 @@ }, "dependencies": { "ethereum-cryptography": "^2.0.0", - "jsonschema": "^1.4.1", "util": "^0.12.5", "web3-errors": "^1.0.0", - "web3-types": "^1.0.0" + "web3-types": "^1.0.0", + "zod": "^3.21.4" }, "devDependencies": { "@types/jest": "^28.1.6", diff --git a/packages/web3-validator/src/errors.ts b/packages/web3-validator/src/errors.ts index 0e8e4125c3a..8faf361248b 100644 --- a/packages/web3-validator/src/errors.ts +++ b/packages/web3-validator/src/errors.ts @@ -18,24 +18,7 @@ along with web3.js. If not, see . import { BaseWeb3Error, ERR_VALIDATION } from 'web3-errors'; import { Web3ValidationErrorObject } from 'web3-types'; -import { isNullish } from './validation/object.js'; - const errorFormatter = (error: Web3ValidationErrorObject): string => { - if (error.message && error.instancePath && error.params && !isNullish(error.params.value)) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - return `value "${(error.params as { value: unknown }).value}" at "${error.instancePath}" ${ - error.message - }`; - } - - if (error.message && error.instancePath) { - return `value at "${error.instancePath}" ${error.message}`; - } - - if (error.instancePath) { - return `value at "${error.instancePath}" caused unspecified error`; - } - if (error.message) { return error.message; } @@ -58,7 +41,6 @@ export class Web3ValidatorError extends BaseWeb3Error { } private _compileErrors(): string[] { - const errorMsgs = this.errors.map(errorFormatter); - return errorMsgs; + return this.errors.map(errorFormatter); } } diff --git a/packages/web3-validator/src/formats.ts b/packages/web3-validator/src/formats.ts index 086b291fea4..da0c0ba6564 100644 --- a/packages/web3-validator/src/formats.ts +++ b/packages/web3-validator/src/formats.ts @@ -17,8 +17,7 @@ along with web3.js. If not, see . import { Filter } from 'web3-types'; import { ValidInputTypes } from './types.js'; import { isAddress } from './validation/address.js'; -import { isBlockNumber,isBlockNumberOrTag, - isBlockTag, } from './validation/block.js'; +import { isBlockNumber, isBlockNumberOrTag, isBlockTag } from './validation/block.js'; import { isBloom } from './validation/bloom.js'; import { isBoolean } from './validation/boolean.js'; import { isBytes } from './validation/bytes.js'; @@ -52,5 +51,6 @@ for (let size = 1; size <= 32; size += 1) { formats[`bytes${size}`] = data => isBytes(data as ValidInputTypes | Uint8Array | number[], { size }); } +formats.bytes256 = formats.bytes; export default formats; diff --git a/packages/web3-validator/src/types.ts b/packages/web3-validator/src/types.ts index a0ecc7b1f7d..59bd9474b43 100644 --- a/packages/web3-validator/src/types.ts +++ b/packages/web3-validator/src/types.ts @@ -16,7 +16,7 @@ along with web3.js. If not, see . */ import { AbiParameter } from 'web3-types'; -import { ValidationError } from 'jsonschema'; +import { ZodIssueBase } from 'zod'; export type ValidInputTypes = Uint8Array | bigint | string | number | boolean; export type EthBaseTypes = 'bool' | 'bytes' | 'string' | 'uint' | 'int' | 'address' | 'tuple'; @@ -143,7 +143,7 @@ export type Schema = { }; export interface Validate { (value: Json): boolean; - errors?: ValidationError[]; + errors?: ZodIssueBase[]; } export type JsonSchema = Schema; diff --git a/packages/web3-validator/src/validator.ts b/packages/web3-validator/src/validator.ts index 842e245f5f0..6fd666d1f24 100644 --- a/packages/web3-validator/src/validator.ts +++ b/packages/web3-validator/src/validator.ts @@ -16,19 +16,80 @@ along with web3.js. If not, see . */ import { Web3ValidationErrorObject } from 'web3-types'; -import { Validator as JsonSchemaValidator, ValidationError } from 'jsonschema'; -import formats from './formats.js'; +import { z, ZodType, ZodIssue, ZodIssueCode, ZodTypeAny } from 'zod'; + +import { RawCreateParams } from 'zod/lib/types'; import { Web3ValidatorError } from './errors.js'; import { Json, Schema } from './types.js'; +import formats from './formats'; -export class Validator { - private readonly internalValidator: JsonSchemaValidator; - private constructor() { - JsonSchemaValidator.prototype.customFormats = formats; - this.internalValidator = new JsonSchemaValidator(); +const convertToZod = (schema: Schema): ZodType => { + if ((!schema?.type || schema?.type === 'object') && schema?.properties) { + const obj: { [key: string]: ZodType } = {}; + for (const name of Object.keys(schema.properties)) { + const zItem = convertToZod(schema.properties[name]); + if (zItem) { + obj[name] = zItem; + } + } + + if (Array.isArray(schema.required)) { + return z + .object(obj) + .partial() + .required(schema.required.reduce((acc, v: string) => ({ ...acc, [v]: true }), {})); + } + return z.object(obj).partial(); + } + + if (schema?.type === 'array' && schema?.items) { + if (Array.isArray(schema.items) && schema.items.length > 0) { + const arr: Partial<[ZodTypeAny, ...ZodTypeAny[]]> = []; + for (const item of schema.items) { + const zItem = convertToZod(item); + if (zItem) { + arr.push(zItem); + } + } + return z.tuple(arr as [ZodTypeAny, ...ZodTypeAny[]]); + } + return z.array(convertToZod(schema.items as Schema)); + } + + if (schema.oneOf && Array.isArray(schema.oneOf)) { + return z.union( + schema.oneOf.map(oneOfSchema => convertToZod(oneOfSchema)) as [ + ZodTypeAny, + ZodTypeAny, + ...ZodTypeAny[], + ], + ); } + + if (schema?.format) { + return z.any().refine(formats[schema.format], (value: unknown) => ({ + params: { value, format: schema.format }, + })); + } + + if ( + schema?.type && + schema?.type !== 'object' && + typeof (z as unknown as { [key: string]: (params?: RawCreateParams) => ZodType })[ + String(schema.type) + ] === 'function' + ) { + return (z as unknown as { [key: string]: (params?: RawCreateParams) => ZodType })[ + String(schema.type) + ](); + } + return z.object({ data: z.any() }).partial(); +}; + +export class Validator { // eslint-disable-next-line no-use-before-define private static validatorInstance?: Validator; + // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function public static factory(): Validator { if (!Validator.validatorInstance) { @@ -38,9 +99,10 @@ export class Validator { } public validate(schema: Schema, data: Json, options?: { silent?: boolean }) { - const validationResult = this.internalValidator.validate(data, schema); - if (!validationResult.valid) { - const errors = this.convertErrors(validationResult.errors); + const zod = convertToZod(schema); + const result = zod.safeParse(data); + if (!result.success) { + const errors = this.convertErrors(result.error?.issues ?? []); if (errors) { if (options?.silent) { return errors; @@ -51,11 +113,9 @@ export class Validator { return undefined; } // eslint-disable-next-line class-methods-use-this - private convertErrors( - errors: ValidationError[] | undefined, - ): Web3ValidationErrorObject[] | undefined { + private convertErrors(errors: ZodIssue[] | undefined): Web3ValidationErrorObject[] | undefined { if (errors && Array.isArray(errors) && errors.length > 0) { - return errors.map((error: ValidationError) => { + return errors.map((error: ZodIssue) => { let message; let keyword; let params; @@ -63,42 +123,42 @@ export class Validator { schemaPath = error.path.join('/'); - const field = error.property; + const field = String(error.path[error.path.length - 1]); const instancePath = error.path.join('/'); - if (error?.message.startsWith('does not meet minimum length of')) { - if (error.argument) { - keyword = 'minItems'; - schemaPath = `${instancePath}/minItems`; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - params = { limit: error.argument }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions - message = `must NOT have fewer than ${error.argument} items`; - } - } else if (error?.message.startsWith('does not meet maximum length of')) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (error.argument) { - keyword = 'maxItems'; - schemaPath = `${instancePath}/maxItems`; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - params = { limit: error.argument }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions - message = `must NOT have more than ${error.argument} items`; - } - } else if ( - error?.message.startsWith('does not conform to the') && - error?.message.endsWith('format') - ) { - const formatName = error?.message.split(' ')[5]; - if (formatName) { - message = `must pass ${formatName} validation`; + if (error.code === ZodIssueCode.too_big) { + keyword = 'maxItems'; + schemaPath = `${instancePath}/maxItems`; + params = { limit: error.maximum }; + message = `must NOT have more than ${error.maximum} items`; + } else if (error.code === ZodIssueCode.too_small) { + keyword = 'minItems'; + schemaPath = `${instancePath}/minItems`; + params = { limit: error.minimum }; + message = `must NOT have fewer than ${error.minimum} items`; + } else if (error.code === ZodIssueCode.custom) { + const { value, format } = (error.params ?? {}) as { + value: unknown; + format: string; + }; + + if (typeof value === 'undefined') { + message = `value at "/${schemaPath}" is required`; + } else { + message = `value "${ + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + typeof value === 'object' ? JSON.stringify(value) : value + }" at "/${schemaPath}" must pass "${format}" validation`; } + + params = { value }; } + return { - keyword: keyword ?? field.replace('instance', 'data'), + keyword: keyword ?? field, instancePath: instancePath ? `/${instancePath}` : '', schemaPath: schemaPath ? `#${schemaPath}` : '#', // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - params: params ?? { value: error.instance }, + params: params ?? { value: error.message }, message: message ?? error.message, } as Web3ValidationErrorObject; }); diff --git a/packages/web3-validator/src/web3_validator.ts b/packages/web3-validator/src/web3_validator.ts index f56cc36e13f..a165a36bef1 100644 --- a/packages/web3-validator/src/web3_validator.ts +++ b/packages/web3-validator/src/web3_validator.ts @@ -23,11 +23,9 @@ import { Web3ValidatorError } from './errors.js'; export class Web3Validator { private readonly _validator: Validator; - public constructor() { this._validator = Validator.factory(); } - public validateJSONSchema( schema: object, data: object, @@ -35,7 +33,6 @@ export class Web3Validator { ): Web3ValidationErrorObject[] | undefined { return this._validator.validate(schema, data as Json, options); } - public validate( schema: ValidationSchemaInput, data: ReadonlyArray, diff --git a/packages/web3-validator/test/fixtures/errors.ts b/packages/web3-validator/test/fixtures/errors.ts index dca0a2bc7e4..9b92474d582 100644 --- a/packages/web3-validator/test/fixtures/errors.ts +++ b/packages/web3-validator/test/fixtures/errors.ts @@ -39,25 +39,6 @@ export const fullErrors: any[] = [ }, ]; -export const fullErrorsWithInstance: any[] = [ - { - message: 'must pass "uint" validation', - keyword: 'eth', - params: { value: -1 }, - instancePath: '/0', - schemaPath: '#/items/0/eth', - }, -]; - -export const errorsWithInstanceNoParams: any[] = [ - { - message: 'must pass "uint" validation', - keyword: 'eth', - instancePath: '/0', - schemaPath: '#/items/0/eth', - }, -]; - export const errorsWithInstanceNoParamsNoMessage: any[] = [ { keyword: 'eth', diff --git a/packages/web3-validator/test/unit/error.test.ts b/packages/web3-validator/test/unit/error.test.ts index 74accbc8ed1..14cf631c7c5 100644 --- a/packages/web3-validator/test/unit/error.test.ts +++ b/packages/web3-validator/test/unit/error.test.ts @@ -19,8 +19,6 @@ import { Web3ValidationErrorObject } from 'web3-types'; import { Web3ValidatorError } from '../../src/errors'; import { fullErrors, - fullErrorsWithInstance, - errorsWithInstanceNoParams, errorsWithInstanceNoParamsNoMessage, unspecifiedErrors, } from '../fixtures/errors'; @@ -33,32 +31,6 @@ describe('Web3ValidationError', () => { expect(validationError.message).toBe(`Web3 validator found 1 error[s]:\n${error.message}`); }); - it.each(fullErrorsWithInstance)( - 'errors with message, instance and params', - (error: Web3ValidationErrorObject) => { - const validationError = new Web3ValidatorError([error]); - - expect(validationError).toBeInstanceOf(Web3ValidatorError); - expect(validationError.message).toBe( - `Web3 validator found 1 error[s]:\nvalue "${ - (error.params as { value: unknown }).value - }" at "${error.instancePath}" ${error.message}`, - ); - }, - ); - - it.each(errorsWithInstanceNoParams)( - 'errors with only message and instance', - (error: Web3ValidationErrorObject) => { - const validationError = new Web3ValidatorError([error]); - - expect(validationError).toBeInstanceOf(Web3ValidatorError); - expect(validationError.message).toBe( - `Web3 validator found 1 error[s]:\nvalue at "${error.instancePath}" ${error.message}`, - ); - }, - ); - it.each(errorsWithInstanceNoParamsNoMessage)( 'errors with only instance', (error: Web3ValidationErrorObject) => { @@ -66,7 +38,7 @@ describe('Web3ValidationError', () => { expect(validationError).toBeInstanceOf(Web3ValidatorError); expect(validationError.message).toBe( - `Web3 validator found 1 error[s]:\nvalue at "${error.instancePath}" caused unspecified error`, + `Web3 validator found 1 error[s]:\nunspecified error`, ); }, ); diff --git a/packages/web3-validator/test/unit/web3_validator.test.ts b/packages/web3-validator/test/unit/web3_validator.test.ts index 29423e0d71e..e1a49fa6e3a 100644 --- a/packages/web3-validator/test/unit/web3_validator.test.ts +++ b/packages/web3-validator/test/unit/web3_validator.test.ts @@ -64,8 +64,8 @@ describe('web3-validator', () => { expect(validator.validate(['uint'], [-1], { silent: true })).toEqual([ { instancePath: '/0', - keyword: 'data[0]', - message: 'must pass "uint" validation', + keyword: '0', + message: 'value "-1" at "/0" must pass "uint" validation', params: { value: -1 }, schemaPath: '#0', }, @@ -81,9 +81,7 @@ describe('web3-validator', () => { const testFunction = () => { validator.validate([], data); }; - expect(testFunction).toThrow( - 'value at "/0" empty schema against data can not be validated', - ); + expect(testFunction).toThrow('empty schema against data can not be validated'); expect(testFunction).toThrow(Web3ValidatorError); }); diff --git a/yarn.lock b/yarn.lock index 46e30d69c54..8dfa1dda670 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7624,11 +7624,6 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonschema@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" - integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== - jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -12082,3 +12077,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.21.4: + version "3.21.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== From 7d621117cd777119753edc227f58b2ff81a956c8 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Tue, 11 Jul 2023 17:33:21 -0400 Subject: [PATCH 03/17] load test --- .husky/pre-commit | 2 +- .../src/errors/transaction_errors.ts | 10 +- packages/web3-validator/src/validator.ts | 119 ++++++++++++++++++ .../web3-validator/test/unit/load.test.ts | 98 +++++++++++++++ 4 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 packages/web3-validator/test/unit/load.test.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index 36af219892f..d5b5fd41c7c 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx lint-staged +#npx lint-staged diff --git a/packages/web3-errors/src/errors/transaction_errors.ts b/packages/web3-errors/src/errors/transaction_errors.ts index c74aebee767..2b7d7791cc8 100644 --- a/packages/web3-errors/src/errors/transaction_errors.ts +++ b/packages/web3-errors/src/errors/transaction_errors.ts @@ -555,15 +555,7 @@ export class InvalidPropertiesForTransactionTypeError extends BaseWeb3Error { txType: '0x0' | '0x1' | '0x2', ) { const invalidPropertyNames: string[] = []; - validationError.forEach(error => - invalidPropertyNames.push( - error.keyword, - // These errors are erroneously reported, error - // has type Web3ValidationErrorObject, but eslint doesn't recognize it - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - // (error.keyword.match(/data.(.+)/) as string[])[1], - ), - ); + validationError.forEach(error => invalidPropertyNames.push(error.keyword)); super( `The following properties are invalid for the transaction type ${txType}: ${invalidPropertyNames.join( ', ', diff --git a/packages/web3-validator/src/validator.ts b/packages/web3-validator/src/validator.ts index 6fd666d1f24..a603eb759b4 100644 --- a/packages/web3-validator/src/validator.ts +++ b/packages/web3-validator/src/validator.ts @@ -22,6 +22,8 @@ import { RawCreateParams } from 'zod/lib/types'; import { Web3ValidatorError } from './errors.js'; import { Json, Schema } from './types.js'; import formats from './formats'; +// import { isAbiParameterSchema } from './validation/abi'; +// import { parseBaseType } from './utils'; const convertToZod = (schema: Schema): ZodType => { if ((!schema?.type || schema?.type === 'object') && schema?.properties) { @@ -86,6 +88,123 @@ const convertToZod = (schema: Schema): ZodType => { return z.object({ data: z.any() }).partial(); }; +// export const abiToZod = (abis: ShortValidationSchema | FullValidationSchema, level = '/0') => { +// for (const [index, abi] of abis.entries()) { +// if (isAbiParameterSchema(abi)) { +// } +// +// // eslint-disable-next-line no-nested-ternary +// let abiType!: string; +// let abiName!: string; +// let abiComponents: ShortValidationSchema | FullValidationSchema | undefined = []; +// +// // If it's a complete Abi Parameter +// // e.g. {name: 'a', type: 'uint'} +// if (isAbiParameterSchema(abi)) { +// z.object({[abi.name]:convertToZod(abi)}).partial(); +// +// convertToZod({ +// type +// properties: { +// [abi.name]: { type: parseBaseType(abiType) }, +// }, +// }); +// abiType = abi.type; +// abiName = abi.name; +// abiComponents = abi.components as FullValidationSchema; +// // If its short form string value e.g. ['uint'] +// } else if (typeof abi === 'string') { +// abiType = abi; +// abiName = `${level}/${index}`; +// +// // If it's provided in short form of tuple e.g. [['uint', 'string']] +// } else if (Array.isArray(abi)) { +// // If its custom tuple e.g. ['tuple[2]', ['uint', 'string']] +// if ( +// abi[0] && +// typeof abi[0] === 'string' && +// abi[0].startsWith('tuple') && +// !Array.isArray(abi[0]) && +// abi[1] && +// Array.isArray(abi[1]) +// ) { +// // eslint-disable-next-line prefer-destructuring +// abiType = abi[0]; +// abiName = `${level}/${index}`; +// abiComponents = abi[1] as ReadonlyArray; +// } else { +// abiType = 'tuple'; +// abiName = `${level}/${index}`; +// abiComponents = abi; +// } +// } +// +// const { baseType, isArray, arraySizes } = parseBaseType(abiType); +// +// let childSchema: ZodType; +// let lastSchema: ZodType; +// for (let i = arraySizes.length - 1; i > 0; i -= 1) { +// childSchema = convertToZod({ +// type: 'array', +// items: [], +// maxItems: arraySizes[i], +// minItems: arraySizes[i], +// }); +// +// lastSchema = childSchema; +// } +// +// if (baseType === 'tuple' && !isArray) { +// const nestedTuple = abiToZod(abiComponents, abiName); +// nestedTuple.$id = abiName; +// (lastSchema.items as JsonSchema[]).push(nestedTuple); +// } else if (baseType === 'tuple' && isArray) { +// const arraySize = arraySizes[0]; +// const item: JsonSchema = { +// $id: abiName, +// type: 'array', +// items: abiToZod(abiComponents, abiName), +// maxItems: arraySize, +// minItems: arraySize, +// }; +// +// if (arraySize < 0) { +// delete item.maxItems; +// delete item.minItems; +// } +// +// (lastSchema.items as JsonSchema[]).push(item); +// } else if (isArray) { +// const arraySize = arraySizes[0]; +// const item: JsonSchema = { +// type: 'array', +// $id: abiName, +// items: convertEthType(String(baseType)), +// minItems: arraySize, +// maxItems: arraySize, +// }; +// +// if (arraySize < 0) { +// delete item.maxItems; +// delete item.minItems; +// } +// +// (lastSchema.items as JsonSchema[]).push(item); +// } else if (Array.isArray(lastSchema.items)) { +// // Array of non-tuple items +// lastSchema.items.push({ $id: abiName, ...convertEthType(abiType) }); +// } else { +// // Nested object +// ((lastSchema.items as JsonSchema).items as JsonSchema[]).push({ +// $id: abiName, +// ...convertEthType(abiType), +// }); +// } +// } +// +// return schema; +// }; + export class Validator { // eslint-disable-next-line no-use-before-define private static validatorInstance?: Validator; diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts new file mode 100644 index 00000000000..d14547365c2 --- /dev/null +++ b/packages/web3-validator/test/unit/load.test.ts @@ -0,0 +1,98 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { Validator } from '../../src/validator'; +import { Json, JsonSchema } from '../../'; + +const simpleSchema = { + type: 'object', + required: ['blockHash', 'blockNumber', 'from', 'to', 'data'], + properties: { + blockHash: { + format: 'bytes32', + }, + blockNumber: { + format: 'uint', + }, + from: { + format: 'address', + }, + to: { + oneOf: [{ format: 'address' }, { type: 'null' }], + }, + data: { + format: 'bytes', + }, + }, +}; +const simpleData = { + blockHash: '0x0dec0518fa672a70027b04c286582e543ab17319fbdd384fa7bc8f3d5a542c0b', + blockNumber: BigInt(2), + from: '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', + to: '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', + data: '0xafea', +}; +// @ts-ignore +const createHugeSchema = (schema: JsonSchema, data: object, n = 3) => { + if (n > 0) { + // @ts-ignore + const { data: resultData, schema: resultSchema } = createHugeSchema( + // @ts-ignore + { ...simpleSchema }, + // @ts-ignore + { ...simpleData }, + n - 1, + ); + return { + data: { ...data, simple: resultData }, + schema: { ...schema, properties: { ...schema.properties, simple: resultSchema } }, + }; + } else { + return { + schema, + data, + }; + } +}; +const { schema: hugeSchema, data: hugeData } = createHugeSchema( + { ...simpleSchema }, + { ...simpleData }, + 1000, +); +describe('instance of validator', () => { + let validator: Validator; + beforeAll(() => { + validator = Validator.factory(); + }); + + it('huge schema', () => { + expect(() => { + console.time('hugeData'); + validator.validate(hugeSchema, hugeData); + console.timeLog('hugeData'); + }).not.toThrow(); + }); + it('simple schema multiple times', () => { + expect(() => { + console.time('hugeData'); + for (let i = 0; i < 1000; i++) { + validator.validate(simpleSchema, simpleData as unknown as Json); + } + console.timeLog('hugeData'); + }).not.toThrow(); + }); +}); From 4ed7b6f0888ece509fae26a39028d40d8cc657a2 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 12 Jul 2023 23:19:09 -0400 Subject: [PATCH 04/17] add load test --- .husky/pre-commit | 2 +- .../web3-validator/test/unit/load.test.ts | 127 ++++++++++++++---- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index d5b5fd41c7c..36af219892f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -#npx lint-staged +npx lint-staged diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index d14547365c2..5113246180b 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -15,9 +15,27 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { Validator } from '../../src/validator'; -import { Json, JsonSchema } from '../../'; +import { Web3Validator } from '../../src/web3_validator'; +import { Json, JsonSchema, ValidationSchemaInput } from '../..'; +const abi = [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, +]; +const abiJsonSchema = { + type: 'array', + items: [ + { name: 'from', format: 'address' }, + { name: 'to', format: 'address' }, + { name: 'value', format: 'uint256' }, + ], +}; +const abiData = [ + '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', + '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', + '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', +]; const simpleSchema = { type: 'object', required: ['blockHash', 'blockNumber', 'from', 'to', 'data'], @@ -45,54 +63,111 @@ const simpleData = { from: '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', to: '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', data: '0xafea', -}; -// @ts-ignore -const createHugeSchema = (schema: JsonSchema, data: object, n = 3) => { +} as unknown as ValidationSchemaInput; +const createHugeSchema = ( + schema: JsonSchema, + data: Json, + n = 3, +): { schema: JsonSchema; data: Json } => { if (n > 0) { - // @ts-ignore const { data: resultData, schema: resultSchema } = createHugeSchema( - // @ts-ignore - { ...simpleSchema }, - // @ts-ignore - { ...simpleData }, + { ...simpleSchema } as JsonSchema, + { ...simpleData } as Json, n - 1, ); return { - data: { ...data, simple: resultData }, + data: { ...(data as unknown as object), simple: resultData }, schema: { ...schema, properties: { ...schema.properties, simple: resultSchema } }, }; - } else { - return { - schema, - data, - }; } + return { + schema, + data, + }; }; const { schema: hugeSchema, data: hugeData } = createHugeSchema( { ...simpleSchema }, - { ...simpleData }, + { ...simpleData } as Json, + 500, +); + +const { schema: hugeSchema1000, data: hugeData1000 } = createHugeSchema( + { ...simpleSchema }, + { ...simpleData } as Json, 1000, ); describe('instance of validator', () => { - let validator: Validator; + let validator: Web3Validator; beforeAll(() => { - validator = Validator.factory(); + validator = new Web3Validator(); }); it('huge schema', () => { + let t = 0; expect(() => { - console.time('hugeData'); - validator.validate(hugeSchema, hugeData); - console.timeLog('hugeData'); + const t1 = Number(new Date()); + validator.validateJSONSchema(hugeSchema, hugeData as object); + t = Number(new Date()) - t1; }).not.toThrow(); + expect(t).toBeLessThan(500); + expect(t).toBeGreaterThan(0); + }); + it('huge schema 1000', () => { + let t = 0; + expect(() => { + const t1 = Number(new Date()); + validator.validateJSONSchema(hugeSchema1000, hugeData1000 as object); + t = Number(new Date()) - t1; + }).not.toThrow(); + expect(t).toBeLessThan(500); + expect(t).toBeGreaterThan(0); }); it('simple schema multiple times', () => { + let t = 0; + expect(() => { + const t1 = Number(new Date()); + for (let i = 0; i < 500; i += 1) { + validator.validateJSONSchema(simpleSchema, simpleData as object); + } + t = Number(new Date()) - t1; + }).not.toThrow(); + expect(t).toBeLessThan(500); + expect(t).toBeGreaterThan(0); + }); + it('simple schema 10000 times', () => { + let t = 0; + expect(() => { + const t1 = Number(new Date()); + for (let i = 0; i < 10000; i += 1) { + validator.validateJSONSchema(simpleSchema, simpleData as object); + } + t = Number(new Date()) - t1; + }).not.toThrow(); + expect(t).toBeLessThan(1000); + expect(t).toBeGreaterThan(0); + }); + it('simple JSON schema 10000 times', () => { + let t = 0; + expect(() => { + const t1 = Number(new Date()); + for (let i = 0; i < 10000; i += 1) { + validator.validateJSONSchema(abiJsonSchema, abiData as object); + } + t = Number(new Date()) - t1; + }).not.toThrow(); + expect(t).toBeLessThan(1000); + expect(t).toBeGreaterThan(0); + }); + it('simple ABI 10000 times', () => { + let t = 0; expect(() => { - console.time('hugeData'); - for (let i = 0; i < 1000; i++) { - validator.validate(simpleSchema, simpleData as unknown as Json); + const t1 = Number(new Date()); + for (let i = 0; i < 10000; i += 1) { + validator.validate(abi, abiData); } - console.timeLog('hugeData'); + t = Number(new Date()) - t1; }).not.toThrow(); + expect(t).toBeLessThan(1000); + expect(t).toBeGreaterThan(0); }); }); From ce9e4623c4e02b0706cce20595d90a65a7cd7dd4 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 12 Jul 2023 23:27:31 -0400 Subject: [PATCH 05/17] add changelog --- CHANGELOG.md | 18 ++++++++++++++++++ packages/web3-validator/CHANGELOG.md | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02e2a87ab6..2b1b63e30ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1776,3 +1776,21 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - Dependencies updated ## [Unreleased] + +### Added + +#### web3-errors + +- `RpcErrorMessages` that contains mapping for standard RPC Errors and their messages. (#6230) + +### Fixed + +#### web3-errors + +- Fixed: "'disconnect' in Eip1193 provider must emit ProviderRpcError #6003".(#6230) + +### Changed + +#### web3-validator + +- Replace `is-my-json-valid` with `zod` dependency. Related code was changed (#6264) diff --git a/packages/web3-validator/CHANGELOG.md b/packages/web3-validator/CHANGELOG.md index 2eb7361d70a..8fa710dc645 100644 --- a/packages/web3-validator/CHANGELOG.md +++ b/packages/web3-validator/CHANGELOG.md @@ -121,3 +121,7 @@ Documentation: - Dependencies updated ## [Unreleased] + +### Changed + +- Replace `is-my-json-valid` with `zod` dependency. Related code was changed (#6264) From 466e93a9c9e58b1c16930d3466daebbbe09e92b7 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 12 Jul 2023 23:47:43 -0400 Subject: [PATCH 06/17] increase test coverage --- .../test/unit/convert_to_zod.test.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 packages/web3-validator/test/unit/convert_to_zod.test.ts diff --git a/packages/web3-validator/test/unit/convert_to_zod.test.ts b/packages/web3-validator/test/unit/convert_to_zod.test.ts new file mode 100644 index 00000000000..358e6a8b801 --- /dev/null +++ b/packages/web3-validator/test/unit/convert_to_zod.test.ts @@ -0,0 +1,79 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { Web3Validator } from '../../src/web3_validator'; + +describe('convert-to-zod', () => { + let validator: Web3Validator; + + beforeAll(() => { + validator = new Web3Validator(); + }); + + it('simple array', () => { + expect(() => + validator.validateJSONSchema( + { + type: 'array', + items: { type: 'string' }, + }, + ['a', 'b', 'c'], + ), + ).not.toThrow(); + }); + + it('simple object', () => { + expect(() => + validator.validateJSONSchema( + { + type: 'object', + properties: { + a: { type: 'number' }, + }, + }, + { a: 1 }, + ), + ).not.toThrow(); + }); + it('incorrect type to object', () => { + expect(() => + validator.validateJSONSchema( + { + type: 'object2', + properties: { + a: { type: 'number' }, + }, + }, + { a: 1 }, + ), + ).not.toThrow(); + }); + it('format with undefined value', () => { + expect(() => + validator.validateJSONSchema( + { + required: ['a'], + type: 'object', + properties: { + a: { format: 'uint' }, + }, + }, + { a: undefined }, + ), + ).toThrow(); + }); +}); From 50db246843da5ec48dce39d95f645fa84b854d18 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jul 2023 09:44:09 -0400 Subject: [PATCH 07/17] fix test timing --- packages/web3-validator/test/unit/load.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index 5113246180b..ba7b4e14c6a 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -109,7 +109,7 @@ describe('instance of validator', () => { validator.validateJSONSchema(hugeSchema, hugeData as object); t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(500); + expect(t).toBeLessThan(3000); expect(t).toBeGreaterThan(0); }); it('huge schema 1000', () => { @@ -119,7 +119,7 @@ describe('instance of validator', () => { validator.validateJSONSchema(hugeSchema1000, hugeData1000 as object); t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(500); + expect(t).toBeLessThan(3000); expect(t).toBeGreaterThan(0); }); it('simple schema multiple times', () => { @@ -131,7 +131,7 @@ describe('instance of validator', () => { } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(500); + expect(t).toBeLessThan(1500); expect(t).toBeGreaterThan(0); }); it('simple schema 10000 times', () => { @@ -146,28 +146,28 @@ describe('instance of validator', () => { expect(t).toBeLessThan(1000); expect(t).toBeGreaterThan(0); }); - it('simple JSON schema 10000 times', () => { + it('simple JSON schema 1000 times', () => { let t = 0; expect(() => { const t1 = Number(new Date()); - for (let i = 0; i < 10000; i += 1) { + for (let i = 0; i < 1000; i += 1) { validator.validateJSONSchema(abiJsonSchema, abiData as object); } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(1000); + expect(t).toBeLessThan(2000); expect(t).toBeGreaterThan(0); }); - it('simple ABI 10000 times', () => { + it('simple ABI 1000 times', () => { let t = 0; expect(() => { const t1 = Number(new Date()); - for (let i = 0; i < 10000; i += 1) { + for (let i = 0; i < 1000; i += 1) { validator.validate(abi, abiData); } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(1000); + expect(t).toBeLessThan(2000); expect(t).toBeGreaterThan(0); }); }); From 0f4ff8b321c996942475002dce19d9de15670a94 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jul 2023 10:24:36 -0400 Subject: [PATCH 08/17] fix test timing --- packages/web3-validator/test/unit/load.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index ba7b4e14c6a..8c3abd1580e 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -134,16 +134,16 @@ describe('instance of validator', () => { expect(t).toBeLessThan(1500); expect(t).toBeGreaterThan(0); }); - it('simple schema 10000 times', () => { + it('simple schema 1000 times', () => { let t = 0; expect(() => { const t1 = Number(new Date()); - for (let i = 0; i < 10000; i += 1) { + for (let i = 0; i < 1000; i += 1) { validator.validateJSONSchema(simpleSchema, simpleData as object); } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(1000); + expect(t).toBeLessThan(2000); expect(t).toBeGreaterThan(0); }); it('simple JSON schema 1000 times', () => { From 0f987138888fb8d4ecd27fad6c4eafacc601762b Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jul 2023 11:04:09 -0400 Subject: [PATCH 09/17] fix timing --- packages/web3-validator/test/unit/load.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index 8c3abd1580e..cac6dff1187 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -109,7 +109,7 @@ describe('instance of validator', () => { validator.validateJSONSchema(hugeSchema, hugeData as object); t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(3000); + expect(t).toBeLessThan(6000); expect(t).toBeGreaterThan(0); }); it('huge schema 1000', () => { @@ -119,7 +119,7 @@ describe('instance of validator', () => { validator.validateJSONSchema(hugeSchema1000, hugeData1000 as object); t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(3000); + expect(t).toBeLessThan(6000); expect(t).toBeGreaterThan(0); }); it('simple schema multiple times', () => { @@ -131,7 +131,7 @@ describe('instance of validator', () => { } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(1500); + expect(t).toBeLessThan(3000); expect(t).toBeGreaterThan(0); }); it('simple schema 1000 times', () => { @@ -143,7 +143,7 @@ describe('instance of validator', () => { } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(2000); + expect(t).toBeLessThan(4000); expect(t).toBeGreaterThan(0); }); it('simple JSON schema 1000 times', () => { @@ -155,7 +155,7 @@ describe('instance of validator', () => { } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(2000); + expect(t).toBeLessThan(4000); expect(t).toBeGreaterThan(0); }); it('simple ABI 1000 times', () => { @@ -167,7 +167,7 @@ describe('instance of validator', () => { } t = Number(new Date()) - t1; }).not.toThrow(); - expect(t).toBeLessThan(2000); + expect(t).toBeLessThan(4000); expect(t).toBeGreaterThan(0); }); }); From e2403b37d0fdedb58d27fc650346fc050cf41bfb Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jul 2023 12:33:52 -0400 Subject: [PATCH 10/17] re-run tests --- packages/web3-validator/test/unit/load.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index cac6dff1187..6792c028dda 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -23,6 +23,7 @@ const abi = [ { indexed: true, internalType: 'address', name: 'to', type: 'address' }, { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, ]; + const abiJsonSchema = { type: 'array', items: [ @@ -31,11 +32,13 @@ const abiJsonSchema = { { name: 'value', format: 'uint256' }, ], }; + const abiData = [ '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', ]; + const simpleSchema = { type: 'object', required: ['blockHash', 'blockNumber', 'from', 'to', 'data'], @@ -57,6 +60,7 @@ const simpleSchema = { }, }, }; + const simpleData = { blockHash: '0x0dec0518fa672a70027b04c286582e543ab17319fbdd384fa7bc8f3d5a542c0b', blockNumber: BigInt(2), @@ -64,6 +68,7 @@ const simpleData = { to: '0xCB00CDE33a7a0Fba30C63745534F1f7Ae607076b', data: '0xafea', } as unknown as ValidationSchemaInput; + const createHugeSchema = ( schema: JsonSchema, data: Json, @@ -85,6 +90,7 @@ const createHugeSchema = ( data, }; }; + const { schema: hugeSchema, data: hugeData } = createHugeSchema( { ...simpleSchema }, { ...simpleData } as Json, @@ -112,6 +118,7 @@ describe('instance of validator', () => { expect(t).toBeLessThan(6000); expect(t).toBeGreaterThan(0); }); + it('huge schema 1000', () => { let t = 0; expect(() => { @@ -122,6 +129,7 @@ describe('instance of validator', () => { expect(t).toBeLessThan(6000); expect(t).toBeGreaterThan(0); }); + it('simple schema multiple times', () => { let t = 0; expect(() => { @@ -134,6 +142,7 @@ describe('instance of validator', () => { expect(t).toBeLessThan(3000); expect(t).toBeGreaterThan(0); }); + it('simple schema 1000 times', () => { let t = 0; expect(() => { @@ -146,6 +155,7 @@ describe('instance of validator', () => { expect(t).toBeLessThan(4000); expect(t).toBeGreaterThan(0); }); + it('simple JSON schema 1000 times', () => { let t = 0; expect(() => { @@ -158,6 +168,7 @@ describe('instance of validator', () => { expect(t).toBeLessThan(4000); expect(t).toBeGreaterThan(0); }); + it('simple ABI 1000 times', () => { let t = 0; expect(() => { From 0b2714a5e18545a98927fecea346b519863854e4 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Fri, 14 Jul 2023 18:43:01 -0400 Subject: [PATCH 11/17] add ValidationError wrapper --- packages/web3-validator/src/types.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/web3-validator/src/types.ts b/packages/web3-validator/src/types.ts index 59bd9474b43..15d9c0154d5 100644 --- a/packages/web3-validator/src/types.ts +++ b/packages/web3-validator/src/types.ts @@ -141,9 +141,12 @@ export type Schema = { readonly eth?: string; items?: Schema | Schema[]; }; + +type ValidationError = ZodIssueBase; + export interface Validate { (value: Json): boolean; - errors?: ZodIssueBase[]; + errors?: ValidationError[]; } export type JsonSchema = Schema; From fbf5a44db917538a80a0d66b12be1bfc7ee78878 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Sun, 16 Jul 2023 12:48:31 -0400 Subject: [PATCH 12/17] add json-schema --- packages/web3-validator/CHANGELOG.md | 5 + packages/web3-validator/package.json | 1 + packages/web3-validator/src/types.ts | 98 ++--------------- packages/web3-validator/src/utils.ts | 9 +- packages/web3-validator/src/validator.ts | 133 ++--------------------- yarn.lock | 2 +- 6 files changed, 25 insertions(+), 223 deletions(-) diff --git a/packages/web3-validator/CHANGELOG.md b/packages/web3-validator/CHANGELOG.md index 8fa710dc645..ddb46f1a914 100644 --- a/packages/web3-validator/CHANGELOG.md +++ b/packages/web3-validator/CHANGELOG.md @@ -125,3 +125,8 @@ Documentation: ### Changed - Replace `is-my-json-valid` with `zod` dependency. Related code was changed (#6264) +- Types `ValidationError` and `JsonSchema` were changed + +### Removed + +- Types RawValidationError was removed diff --git a/packages/web3-validator/package.json b/packages/web3-validator/package.json index f03918f7fee..34435572bae 100644 --- a/packages/web3-validator/package.json +++ b/packages/web3-validator/package.json @@ -46,6 +46,7 @@ }, "dependencies": { "ethereum-cryptography": "^2.0.0", + "json-schema": "^0.4.0", "util": "^0.12.5", "web3-errors": "^1.0.2", "web3-types": "^1.0.2", diff --git a/packages/web3-validator/src/types.ts b/packages/web3-validator/src/types.ts index 15d9c0154d5..147002bdd29 100644 --- a/packages/web3-validator/src/types.ts +++ b/packages/web3-validator/src/types.ts @@ -17,6 +17,7 @@ along with web3.js. If not, see . import { AbiParameter } from 'web3-types'; import { ZodIssueBase } from 'zod'; +import { JSONSchema4 } from 'json-schema'; export type ValidInputTypes = Uint8Array | bigint | string | number | boolean; export type EthBaseTypes = 'bool' | 'bytes' | 'string' | 'uint' | 'int' | 'address' | 'tuple'; @@ -44,12 +45,7 @@ export type EthExtendedTypes = export type FullValidationSchema = ReadonlyArray; export type ShortValidationSchema = ReadonlyArray< - | string - | EthBaseTypes - | EthExtendedTypes - | EthBaseTypesWithMeta - | EthBaseTypesWithMeta - | ShortValidationSchema + string | EthBaseTypes | EthExtendedTypes | EthBaseTypesWithMeta | ShortValidationSchema >; export type ValidationSchemaInput = FullValidationSchema | ShortValidationSchema; @@ -57,96 +53,16 @@ export type Web3ValidationOptions = { readonly silent: boolean; }; -// is-my-json-valid types export type Json = string | number | boolean | Array | { [id: string]: Json }; -export type Schema = { - // version - $schema?: string; - $vocabulary?: string; - // pointers - id?: string; - $id?: string; - $anchor?: string; - $ref?: string; - definitions?: { [id: string]: Schema }; - $defs?: { [id: string]: Schema }; - $recursiveRef?: string; - $recursiveAnchor?: boolean; - // generic - type?: string | Array; - required?: Array | boolean; - default?: Json; - // constant values - enum?: Array; - const?: Json; - // logical checks - not?: Schema; - allOf?: Array; - anyOf?: Array; - oneOf?: Array; - if?: Schema; - then?: Schema; - else?: Schema; - // numbers - maximum?: number; - minimum?: number; - exclusiveMaximum?: number | boolean; - exclusiveMinimum?: number | boolean; - multipleOf?: number; - divisibleBy?: number; - // arrays, basic - maxItems?: number; - minItems?: number; - additionalItems?: Schema; - // arrays, complex - contains?: Schema; - minContains?: number; - maxContains?: number; - uniqueItems?: boolean; - // strings - maxLength?: number; - minLength?: number; - format?: string; - pattern?: string; - // strings content - contentEncoding?: string; - contentMediaType?: string; - contentSchema?: Schema; - // objects - properties?: { [id: string]: Schema }; - maxProperties?: number; - minProperties?: number; - additionalProperties?: Schema; - patternProperties?: { [pattern: string]: Schema }; - propertyNames?: Schema; - dependencies?: { [id: string]: Array | Schema }; - dependentRequired?: { [id: string]: Array }; - dependentSchemas?: { [id: string]: Schema }; - // see-through - unevaluatedProperties?: Schema; - unevaluatedItems?: Schema; - // Unused meta keywords not affecting validation (annotations and comments) - // https://json-schema.org/understanding-json-schema/reference/generic.html - // https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9 - title?: string; - description?: string; - deprecated?: boolean; - readOnly?: boolean; - writeOnly?: boolean; - examples?: Array; - $comment?: string; - // optimization hint and error filtering only, does not affect validation result - discriminator?: { propertyName: string; mapping?: { [value: string]: string } }; - readonly eth?: string; - items?: Schema | Schema[]; -}; - -type ValidationError = ZodIssueBase; +export type ValidationError = ZodIssueBase; export interface Validate { (value: Json): boolean; errors?: ValidationError[]; } +type Schema = JSONSchema4 & { format?: string }; -export type JsonSchema = Schema; +export type JsonSchema = Schema & { items?: Array | Schema } & { + properties?: { [key: string]: Schema }; +}; diff --git a/packages/web3-validator/src/utils.ts b/packages/web3-validator/src/utils.ts index b6e233c98b8..f89ef082718 100644 --- a/packages/web3-validator/src/utils.ts +++ b/packages/web3-validator/src/utils.ts @@ -20,7 +20,6 @@ import { VALID_ETH_BASE_TYPES } from './constants.js'; import { FullValidationSchema, JsonSchema, - Schema, ShortValidationSchema, ValidationSchemaInput, ValidInputTypes, @@ -78,7 +77,7 @@ export const parseBaseType = ( const convertEthType = ( type: string, - parentSchema: Schema = {}, + parentSchema: JsonSchema = {}, ): { format?: string; required?: boolean } => { const typePropertyPresent = Object.keys(parentSchema).includes('type'); @@ -124,7 +123,7 @@ const convertEthType = ( export const abiSchemaToJsonSchema = ( abis: ShortValidationSchema | FullValidationSchema, level = '/0', -) => { +): JsonSchema => { const schema: JsonSchema = { type: 'array', items: [], @@ -174,7 +173,7 @@ export const abiSchemaToJsonSchema = ( const { baseType, isArray, arraySizes } = parseBaseType(abiType); let childSchema: JsonSchema; - let lastSchema = schema; + let lastSchema: JsonSchema = schema; for (let i = arraySizes.length - 1; i > 0; i -= 1) { childSchema = { type: 'array', @@ -230,7 +229,7 @@ export const abiSchemaToJsonSchema = ( (lastSchema.items as JsonSchema[]).push(item); } else if (Array.isArray(lastSchema.items)) { // Array of non-tuple items - lastSchema.items.push({ $id: abiName, ...convertEthType(abiType) }); + lastSchema.items.push({ $id: abiName, ...convertEthType(abiType) } as JsonSchema); } else { // Nested object ((lastSchema.items as JsonSchema).items as JsonSchema[]).push({ diff --git a/packages/web3-validator/src/validator.ts b/packages/web3-validator/src/validator.ts index a603eb759b4..518444b0544 100644 --- a/packages/web3-validator/src/validator.ts +++ b/packages/web3-validator/src/validator.ts @@ -20,16 +20,14 @@ import { z, ZodType, ZodIssue, ZodIssueCode, ZodTypeAny } from 'zod'; import { RawCreateParams } from 'zod/lib/types'; import { Web3ValidatorError } from './errors.js'; -import { Json, Schema } from './types.js'; +import { Json, JsonSchema } from './types.js'; import formats from './formats'; -// import { isAbiParameterSchema } from './validation/abi'; -// import { parseBaseType } from './utils'; -const convertToZod = (schema: Schema): ZodType => { +const convertToZod = (schema: JsonSchema): ZodType => { if ((!schema?.type || schema?.type === 'object') && schema?.properties) { const obj: { [key: string]: ZodType } = {}; for (const name of Object.keys(schema.properties)) { - const zItem = convertToZod(schema.properties[name]); + const zItem = convertToZod(schema.properties[name] as JsonSchema); if (zItem) { obj[name] = zItem; } @@ -48,19 +46,19 @@ const convertToZod = (schema: Schema): ZodType => { if (Array.isArray(schema.items) && schema.items.length > 0) { const arr: Partial<[ZodTypeAny, ...ZodTypeAny[]]> = []; for (const item of schema.items) { - const zItem = convertToZod(item); + const zItem = convertToZod(item as JsonSchema); if (zItem) { arr.push(zItem); } } return z.tuple(arr as [ZodTypeAny, ...ZodTypeAny[]]); } - return z.array(convertToZod(schema.items as Schema)); + return z.array(convertToZod(schema.items as JsonSchema)); } if (schema.oneOf && Array.isArray(schema.oneOf)) { return z.union( - schema.oneOf.map(oneOfSchema => convertToZod(oneOfSchema)) as [ + schema.oneOf.map(oneOfSchema => convertToZod(oneOfSchema as JsonSchema)) as [ ZodTypeAny, ZodTypeAny, ...ZodTypeAny[], @@ -88,123 +86,6 @@ const convertToZod = (schema: Schema): ZodType => { return z.object({ data: z.any() }).partial(); }; -// export const abiToZod = (abis: ShortValidationSchema | FullValidationSchema, level = '/0') => { -// for (const [index, abi] of abis.entries()) { -// if (isAbiParameterSchema(abi)) { -// } -// -// // eslint-disable-next-line no-nested-ternary -// let abiType!: string; -// let abiName!: string; -// let abiComponents: ShortValidationSchema | FullValidationSchema | undefined = []; -// -// // If it's a complete Abi Parameter -// // e.g. {name: 'a', type: 'uint'} -// if (isAbiParameterSchema(abi)) { -// z.object({[abi.name]:convertToZod(abi)}).partial(); -// -// convertToZod({ -// type -// properties: { -// [abi.name]: { type: parseBaseType(abiType) }, -// }, -// }); -// abiType = abi.type; -// abiName = abi.name; -// abiComponents = abi.components as FullValidationSchema; -// // If its short form string value e.g. ['uint'] -// } else if (typeof abi === 'string') { -// abiType = abi; -// abiName = `${level}/${index}`; -// -// // If it's provided in short form of tuple e.g. [['uint', 'string']] -// } else if (Array.isArray(abi)) { -// // If its custom tuple e.g. ['tuple[2]', ['uint', 'string']] -// if ( -// abi[0] && -// typeof abi[0] === 'string' && -// abi[0].startsWith('tuple') && -// !Array.isArray(abi[0]) && -// abi[1] && -// Array.isArray(abi[1]) -// ) { -// // eslint-disable-next-line prefer-destructuring -// abiType = abi[0]; -// abiName = `${level}/${index}`; -// abiComponents = abi[1] as ReadonlyArray; -// } else { -// abiType = 'tuple'; -// abiName = `${level}/${index}`; -// abiComponents = abi; -// } -// } -// -// const { baseType, isArray, arraySizes } = parseBaseType(abiType); -// -// let childSchema: ZodType; -// let lastSchema: ZodType; -// for (let i = arraySizes.length - 1; i > 0; i -= 1) { -// childSchema = convertToZod({ -// type: 'array', -// items: [], -// maxItems: arraySizes[i], -// minItems: arraySizes[i], -// }); -// -// lastSchema = childSchema; -// } -// -// if (baseType === 'tuple' && !isArray) { -// const nestedTuple = abiToZod(abiComponents, abiName); -// nestedTuple.$id = abiName; -// (lastSchema.items as JsonSchema[]).push(nestedTuple); -// } else if (baseType === 'tuple' && isArray) { -// const arraySize = arraySizes[0]; -// const item: JsonSchema = { -// $id: abiName, -// type: 'array', -// items: abiToZod(abiComponents, abiName), -// maxItems: arraySize, -// minItems: arraySize, -// }; -// -// if (arraySize < 0) { -// delete item.maxItems; -// delete item.minItems; -// } -// -// (lastSchema.items as JsonSchema[]).push(item); -// } else if (isArray) { -// const arraySize = arraySizes[0]; -// const item: JsonSchema = { -// type: 'array', -// $id: abiName, -// items: convertEthType(String(baseType)), -// minItems: arraySize, -// maxItems: arraySize, -// }; -// -// if (arraySize < 0) { -// delete item.maxItems; -// delete item.minItems; -// } -// -// (lastSchema.items as JsonSchema[]).push(item); -// } else if (Array.isArray(lastSchema.items)) { -// // Array of non-tuple items -// lastSchema.items.push({ $id: abiName, ...convertEthType(abiType) }); -// } else { -// // Nested object -// ((lastSchema.items as JsonSchema).items as JsonSchema[]).push({ -// $id: abiName, -// ...convertEthType(abiType), -// }); -// } -// } -// -// return schema; -// }; - export class Validator { // eslint-disable-next-line no-use-before-define private static validatorInstance?: Validator; @@ -217,7 +98,7 @@ export class Validator { return Validator.validatorInstance; } - public validate(schema: Schema, data: Json, options?: { silent?: boolean }) { + public validate(schema: JsonSchema, data: Json, options?: { silent?: boolean }) { const zod = convertToZod(schema); const result = zod.safeParse(data); if (!result.success) { diff --git a/yarn.lock b/yarn.lock index e8fa06d9df0..916d7260e3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7537,7 +7537,7 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.4.0: +json-schema@0.4.0, json-schema@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== From a40462aa6546bf715fa298efb0ea7b1a7d4fbfe9 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Sun, 16 Jul 2023 12:56:59 -0400 Subject: [PATCH 13/17] add change log --- packages/web3-validator/CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/web3-validator/CHANGELOG.md b/packages/web3-validator/CHANGELOG.md index ddb46f1a914..e7ac6ee1124 100644 --- a/packages/web3-validator/CHANGELOG.md +++ b/packages/web3-validator/CHANGELOG.md @@ -125,8 +125,13 @@ Documentation: ### Changed - Replace `is-my-json-valid` with `zod` dependency. Related code was changed (#6264) -- Types `ValidationError` and `JsonSchema` were changed +- Types `ValidationError` and `JsonSchema` were changed (#6264) ### Removed -- Types RawValidationError was removed +- Types RawValidationError was removed (#6264) +- Methods `getSchema`, `addSchema`, `getOrCreateValidator`, `getKey` was removed from `Validator` class + +### Added + +- Added `json-schema` as a main json schema type (#6264) From a90dbc3345bf6d65c1010f52afe83058906a88fc Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Sun, 16 Jul 2023 12:59:02 -0400 Subject: [PATCH 14/17] fix change log --- packages/web3-validator/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/web3-validator/CHANGELOG.md b/packages/web3-validator/CHANGELOG.md index e7ac6ee1124..e38bdf709a5 100644 --- a/packages/web3-validator/CHANGELOG.md +++ b/packages/web3-validator/CHANGELOG.md @@ -129,8 +129,7 @@ Documentation: ### Removed -- Types RawValidationError was removed (#6264) -- Methods `getSchema`, `addSchema`, `getOrCreateValidator`, `getKey` was removed from `Validator` class +- Type `RawValidationError` was removed (#6264) ### Added From 3827ca0f02897012b4590a9a87263f76933f9910 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Sun, 16 Jul 2023 19:10:56 -0400 Subject: [PATCH 15/17] revert type changes --- packages/web3-eth/src/schemas.ts | 1 + packages/web3-validator/package.json | 1 - packages/web3-validator/src/types.ts | 86 ++++++++++++++++++- packages/web3-validator/src/utils.ts | 6 +- packages/web3-validator/src/validator.ts | 6 +- .../web3-validator/test/unit/load.test.ts | 4 +- yarn.lock | 2 +- 7 files changed, 92 insertions(+), 14 deletions(-) diff --git a/packages/web3-eth/src/schemas.ts b/packages/web3-eth/src/schemas.ts index f5b200204db..ca431596deb 100644 --- a/packages/web3-eth/src/schemas.ts +++ b/packages/web3-eth/src/schemas.ts @@ -14,6 +14,7 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ + export const accessListItemSchema = { type: 'object', properties: { diff --git a/packages/web3-validator/package.json b/packages/web3-validator/package.json index 34435572bae..f03918f7fee 100644 --- a/packages/web3-validator/package.json +++ b/packages/web3-validator/package.json @@ -46,7 +46,6 @@ }, "dependencies": { "ethereum-cryptography": "^2.0.0", - "json-schema": "^0.4.0", "util": "^0.12.5", "web3-errors": "^1.0.2", "web3-types": "^1.0.2", diff --git a/packages/web3-validator/src/types.ts b/packages/web3-validator/src/types.ts index 147002bdd29..1a179e83b58 100644 --- a/packages/web3-validator/src/types.ts +++ b/packages/web3-validator/src/types.ts @@ -17,7 +17,6 @@ along with web3.js. If not, see . import { AbiParameter } from 'web3-types'; import { ZodIssueBase } from 'zod'; -import { JSONSchema4 } from 'json-schema'; export type ValidInputTypes = Uint8Array | bigint | string | number | boolean; export type EthBaseTypes = 'bool' | 'bytes' | 'string' | 'uint' | 'int' | 'address' | 'tuple'; @@ -61,8 +60,87 @@ export interface Validate { (value: Json): boolean; errors?: ValidationError[]; } -type Schema = JSONSchema4 & { format?: string }; -export type JsonSchema = Schema & { items?: Array | Schema } & { - properties?: { [key: string]: Schema }; +export type Schema = { + // version + $schema?: string; + $vocabulary?: string; + // pointers + id?: string; + $id?: string; + $anchor?: string; + $ref?: string; + definitions?: { [id: string]: Schema }; + $defs?: { [id: string]: Schema }; + $recursiveRef?: string; + $recursiveAnchor?: boolean; + // generic + type?: string | Array; + required?: Array | boolean; + default?: Json; + // constant values + enum?: Array; + const?: Json; + // logical checks + not?: Schema; + allOf?: Array; + anyOf?: Array; + oneOf?: Array; + if?: Schema; + then?: Schema; + else?: Schema; + // numbers + maximum?: number; + minimum?: number; + exclusiveMaximum?: number | boolean; + exclusiveMinimum?: number | boolean; + multipleOf?: number; + divisibleBy?: number; + // arrays, basic + maxItems?: number; + minItems?: number; + additionalItems?: Schema; + // arrays, complex + contains?: Schema; + minContains?: number; + maxContains?: number; + uniqueItems?: boolean; + // strings + maxLength?: number; + minLength?: number; + format?: string; + pattern?: string; + // strings content + contentEncoding?: string; + contentMediaType?: string; + contentSchema?: Schema; + // objects + properties?: { [id: string]: Schema }; + maxProperties?: number; + minProperties?: number; + additionalProperties?: Schema; + patternProperties?: { [pattern: string]: Schema }; + propertyNames?: Schema; + dependencies?: { [id: string]: Array | Schema }; + dependentRequired?: { [id: string]: Array }; + dependentSchemas?: { [id: string]: Schema }; + // see-through + unevaluatedProperties?: Schema; + unevaluatedItems?: Schema; + // Unused meta keywords not affecting validation (annotations and comments) + // https://json-schema.org/understanding-json-schema/reference/generic.html + // https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9 + title?: string; + description?: string; + deprecated?: boolean; + readOnly?: boolean; + writeOnly?: boolean; + examples?: Array; + $comment?: string; + // optimization hint and error filtering only, does not affect validation result + discriminator?: { propertyName: string; mapping?: { [value: string]: string } }; + readonly eth?: string; + items?: Schema | Schema[]; }; + +export type JsonSchema = Schema; diff --git a/packages/web3-validator/src/utils.ts b/packages/web3-validator/src/utils.ts index f89ef082718..93191c2fb1d 100644 --- a/packages/web3-validator/src/utils.ts +++ b/packages/web3-validator/src/utils.ts @@ -123,7 +123,7 @@ const convertEthType = ( export const abiSchemaToJsonSchema = ( abis: ShortValidationSchema | FullValidationSchema, level = '/0', -): JsonSchema => { +) => { const schema: JsonSchema = { type: 'array', items: [], @@ -173,7 +173,7 @@ export const abiSchemaToJsonSchema = ( const { baseType, isArray, arraySizes } = parseBaseType(abiType); let childSchema: JsonSchema; - let lastSchema: JsonSchema = schema; + let lastSchema = schema; for (let i = arraySizes.length - 1; i > 0; i -= 1) { childSchema = { type: 'array', @@ -229,7 +229,7 @@ export const abiSchemaToJsonSchema = ( (lastSchema.items as JsonSchema[]).push(item); } else if (Array.isArray(lastSchema.items)) { // Array of non-tuple items - lastSchema.items.push({ $id: abiName, ...convertEthType(abiType) } as JsonSchema); + lastSchema.items.push({ $id: abiName, ...convertEthType(abiType) }); } else { // Nested object ((lastSchema.items as JsonSchema).items as JsonSchema[]).push({ diff --git a/packages/web3-validator/src/validator.ts b/packages/web3-validator/src/validator.ts index 518444b0544..805b2665ce5 100644 --- a/packages/web3-validator/src/validator.ts +++ b/packages/web3-validator/src/validator.ts @@ -27,7 +27,7 @@ const convertToZod = (schema: JsonSchema): ZodType => { if ((!schema?.type || schema?.type === 'object') && schema?.properties) { const obj: { [key: string]: ZodType } = {}; for (const name of Object.keys(schema.properties)) { - const zItem = convertToZod(schema.properties[name] as JsonSchema); + const zItem = convertToZod(schema.properties[name]); if (zItem) { obj[name] = zItem; } @@ -46,7 +46,7 @@ const convertToZod = (schema: JsonSchema): ZodType => { if (Array.isArray(schema.items) && schema.items.length > 0) { const arr: Partial<[ZodTypeAny, ...ZodTypeAny[]]> = []; for (const item of schema.items) { - const zItem = convertToZod(item as JsonSchema); + const zItem = convertToZod(item); if (zItem) { arr.push(zItem); } @@ -58,7 +58,7 @@ const convertToZod = (schema: JsonSchema): ZodType => { if (schema.oneOf && Array.isArray(schema.oneOf)) { return z.union( - schema.oneOf.map(oneOfSchema => convertToZod(oneOfSchema as JsonSchema)) as [ + schema.oneOf.map(oneOfSchema => convertToZod(oneOfSchema)) as [ ZodTypeAny, ZodTypeAny, ...ZodTypeAny[], diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index 6792c028dda..919214ef7af 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -92,13 +92,13 @@ const createHugeSchema = ( }; const { schema: hugeSchema, data: hugeData } = createHugeSchema( - { ...simpleSchema }, + { ...simpleSchema } as JsonSchema, { ...simpleData } as Json, 500, ); const { schema: hugeSchema1000, data: hugeData1000 } = createHugeSchema( - { ...simpleSchema }, + { ...simpleSchema } as JsonSchema, { ...simpleData } as Json, 1000, ); diff --git a/yarn.lock b/yarn.lock index 6f4e63ac89d..729d09c7273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7537,7 +7537,7 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.4.0, json-schema@^0.4.0: +json-schema@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== From d7468720fb7bd7cdae68584a76edbb73dddf5dc1 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jul 2023 12:16:15 -0400 Subject: [PATCH 16/17] increase coverage --- packages/web3-validator/src/validation/boolean.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/web3-validator/src/validation/boolean.ts b/packages/web3-validator/src/validation/boolean.ts index b16c7aab502..17593c8312c 100644 --- a/packages/web3-validator/src/validation/boolean.ts +++ b/packages/web3-validator/src/validation/boolean.ts @@ -35,9 +35,6 @@ export const isBoolean = (value: ValidInputTypes) => { return value === '0x1' || value === '0x0'; } - if (typeof value === 'number') { - return value === 1 || value === 0; - } - - return false; + // type === number + return value === 1 || value === 0; }; From 76425639992e245d571aae64b359fedc6c6d350d Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jul 2023 12:21:20 -0400 Subject: [PATCH 17/17] changelog sync --- CHANGELOG.md | 22 ++++++++++++++++++++++ packages/web3-eth/src/schemas.ts | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b1b63e30ed..d793b37b407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1783,14 +1783,36 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - `RpcErrorMessages` that contains mapping for standard RPC Errors and their messages. (#6230) +#### web3-validator + +- Added `json-schema` as a main json schema type (#6264) + ### Fixed +#### web3-core + +- Fixed the issue: "Version 4.x does not fire connected event for subscriptions. #6252". (#6262) + #### web3-errors - Fixed: "'disconnect' in Eip1193 provider must emit ProviderRpcError #6003".(#6230) ### Changed +#### web3-core + +- No need to pass `CommonSubscriptionEvents &` at every child class of `Web3Subscription` (#6262) +- Implementation of `_processSubscriptionResult` and `_processSubscriptionError` has been written in the base class `Web3Subscription` and maid `public`. (#6262) +- A new optional protected method `formatSubscriptionResult` could be used to customize data formatting instead of re-implementing `_processSubscriptionResult`. (#6262) +- No more needed to pass `CommonSubscriptionEvents & ` for the first generic parameter of `Web3Subscription` when inheriting from it. (#6262) + #### web3-validator - Replace `is-my-json-valid` with `zod` dependency. Related code was changed (#6264) +- Types `ValidationError` and `JsonSchema` were changed (#6264) + +### Removed + +#### web3-validator + +- Type `RawValidationError` was removed (#6264) diff --git a/packages/web3-eth/src/schemas.ts b/packages/web3-eth/src/schemas.ts index ca431596deb..f5b200204db 100644 --- a/packages/web3-eth/src/schemas.ts +++ b/packages/web3-eth/src/schemas.ts @@ -14,7 +14,6 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ - export const accessListItemSchema = { type: 'object', properties: {