From 1d35a157b445ebe5df093f0613a787f08af7aec4 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Fri, 4 Aug 2023 15:24:03 -0700 Subject: [PATCH 1/2] feature: add new error type for param validation --- src/bulk-load.ts | 4 +++- src/connection.ts | 29 +++++++++++++++-------------- src/errors.ts | 15 +++++++++++++++ src/request.ts | 4 ++-- test/integration/bulk-load-test.js | 14 +++++++++----- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/bulk-load.ts b/src/bulk-load.ts index b1648b0e7..0b892f5b9 100644 --- a/src/bulk-load.ts +++ b/src/bulk-load.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'events'; import WritableTrackingBuffer from './tracking-buffer/writable-tracking-buffer'; import Connection, { InternalConnectionOptions } from './connection'; +import { ParameterValidationError } from './errors'; import { Transform } from 'stream'; import { TYPE as TOKEN_TYPE } from './token/token'; @@ -185,7 +186,8 @@ class RowTransform extends Transform { try { value = c.type.validate(value, c.collation); } catch (error: any) { - return callback(error); + const validateError = new ParameterValidationError(error.message, c.name, value); + return callback(validateError); } } diff --git a/src/connection.ts b/src/connection.ts index 704386af9..dd2d746d3 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -31,7 +31,7 @@ import SqlBatchPayload from './sqlbatch-payload'; import MessageIO from './message-io'; import { Parser as TokenStreamParser } from './token/token-stream-parser'; import { Transaction, ISOLATION_LEVEL, assertValidIsolationLevel } from './transaction'; -import { ConnectionError, RequestError } from './errors'; +import { ConnectionError, RequestError, ParameterValidationError } from './errors'; import { connectInParallel, connectInSequence } from './connector'; import { name as libraryName } from './library'; import { versions } from './tds-versions'; @@ -2829,26 +2829,27 @@ class Connection extends EventEmitter { scale: undefined }); - try { - for (let i = 0, len = request.parameters.length; i < len; i++) { - const parameter = request.parameters[i]; + for (let i = 0, len = request.parameters.length; i < len; i++) { + const parameter = request.parameters[i]; + const value = parameters ? parameters[parameter.name] : null; + try { executeParameters.push({ ...parameter, - value: parameter.type.validate(parameters ? parameters[parameter.name] : null, this.databaseCollation) + value: parameter.type.validate(value, this.databaseCollation) }); - } - } catch (error: any) { - request.error = error; + } catch (error: any) { + const validateError = new ParameterValidationError(error.message, parameter.name, value); + request.error = validateError; - process.nextTick(() => { - this.debug.log(error.message); - request.callback(error); - }); + process.nextTick(() => { + this.debug.log(validateError.message); + request.callback(validateError); + }); - return; + return; + } } - this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(Procedures.Sp_Execute, executeParameters, this.currentTransactionDescriptor(), this.config.options, this.databaseCollation)); } diff --git a/src/errors.ts b/src/errors.ts index 7ffc33b51..b83e2e6f4 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -26,3 +26,18 @@ export class RequestError extends Error { this.code = code; } } + +export class ParameterValidationError extends Error { + code: string | undefined; + + paramName: string | undefined; + paramValue: any | undefined; + + constructor(message: string, paramName: string, paramValue: any, code?: string) { + super(message); + this.paramName = paramName; + this.paramValue = paramValue; + this.message = `Validation failed for parameter:"${paramName}" with value:"${paramValue}" and message:"${message}"`; + this.code = code; + } +} diff --git a/src/request.ts b/src/request.ts index eeb35cb96..9baff8a61 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import { Parameter, DataType } from './data-type'; -import { RequestError } from './errors'; +import { ParameterValidationError } from './errors'; import Connection from './connection'; import { Metadata } from './metadata-parser'; @@ -463,7 +463,7 @@ class Request extends EventEmitter { try { parameter.value = parameter.type.validate(parameter.value, collation); } catch (error: any) { - throw new RequestError('Validation failed for parameter \'' + parameter.name + '\'. ' + error.message, 'EPARAM'); + throw new ParameterValidationError(error.message, parameter.name, parameter.value); } } } diff --git a/test/integration/bulk-load-test.js b/test/integration/bulk-load-test.js index b14ec7892..35d59ac1b 100644 --- a/test/integration/bulk-load-test.js +++ b/test/integration/bulk-load-test.js @@ -7,7 +7,7 @@ const assert = require('chai').assert; const TYPES = require('../../src/data-type').typeByName; import Connection from '../../src/connection'; -import { RequestError } from '../../src/errors'; +import { RequestError, ParameterValidationError } from '../../src/errors'; import Request from '../../src/request'; const debugMode = false; @@ -1525,8 +1525,10 @@ describe('BulkLoad', function() { * @param {undefined | number} rowCount */ function completeBulkLoad(err, rowCount) { - assert.instanceOf(err, TypeError); - assert.strictEqual(/** @type {TypeError} */(err).message, 'Invalid date.'); + assert.instanceOf(err, ParameterValidationError); + assert.strictEqual(/** @type {ParameterValidationError} */(err).paramName, 'value'); + assert.strictEqual(/** @type {ParameterValidationError} */(err).paramValue, 'invalid date'); + assert.strictEqual(/** @type {ParameterValidationError} */(err).message, 'Validation failed for parameter:"value" with value:"invalid date" and message:"Invalid date."'); done(); } @@ -1545,8 +1547,10 @@ describe('BulkLoad', function() { * @param {undefined | number} rowCount */ function completeBulkLoad(err, rowCount) { - assert.instanceOf(err, TypeError); - assert.strictEqual(/** @type {TypeError} */(err).message, 'Invalid date.'); + assert.instanceOf(err, ParameterValidationError); + assert.strictEqual(/** @type {ParameterValidationError} */(err).paramName, 'value'); + assert.strictEqual(/** @type {ParameterValidationError} */(err).paramValue, 'invalid date'); + assert.strictEqual(/** @type {ParameterValidationError} */(err).message, 'Validation failed for parameter:"value" with value:"invalid date" and message:"Invalid date."'); assert.strictEqual(rowCount, 0); From bf2b891c30f9855f1e3bbcdb678a6a69699c2481 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Wed, 6 Sep 2023 13:57:35 -0700 Subject: [PATCH 2/2] chore: use error cause alter the new error --- src/errors.ts | 8 ++------ src/request.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index b83e2e6f4..12469ede4 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -27,17 +27,13 @@ export class RequestError extends Error { } } -export class ParameterValidationError extends Error { - code: string | undefined; - +export class ParameterValidationError extends TypeError { paramName: string | undefined; paramValue: any | undefined; - constructor(message: string, paramName: string, paramValue: any, code?: string) { + constructor(message: string, paramName: string, paramValue: any) { super(message); this.paramName = paramName; this.paramValue = paramValue; - this.message = `Validation failed for parameter:"${paramName}" with value:"${paramValue}" and message:"${message}"`; - this.code = code; } } diff --git a/src/request.ts b/src/request.ts index c32937b15..6df495e5f 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'events'; -import { ParameterValidationError } from './errors'; +import { RequestError, ParameterValidationError } from './errors'; import { type Parameter, type DataType } from './data-type'; import Connection from './connection'; @@ -463,7 +463,11 @@ class Request extends EventEmitter { try { parameter.value = parameter.type.validate(parameter.value, collation); } catch (error: any) { - throw new ParameterValidationError(error.message, parameter.name, parameter.value); + const paramvalidationErr = new ParameterValidationError(error.message, parameter.name, parameter.value); + const requestErr = new RequestError('Validation failed for parameter \'' + parameter.name + '\'. ' + error.message, 'EPARAM'); + requestErr.cause = paramvalidationErr; + + throw requestErr; } } }