diff --git a/src/bulk-load.ts b/src/bulk-load.ts index 380e067ff..6a007b235 100644 --- a/src/bulk-load.ts +++ b/src/bulk-load.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'events'; import WritableTrackingBuffer from './tracking-buffer/writable-tracking-buffer'; +import { ParameterValidationError } from './errors'; import Connection, { type InternalConnectionOptions } from './connection'; import { Transform } from 'stream'; @@ -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 ec677b047..a4e4f2e8a 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..12469ede4 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -26,3 +26,14 @@ export class RequestError extends Error { this.code = code; } } + +export class ParameterValidationError extends TypeError { + paramName: string | undefined; + paramValue: any | undefined; + + constructor(message: string, paramName: string, paramValue: any) { + super(message); + this.paramName = paramName; + this.paramValue = paramValue; + } +} diff --git a/src/request.ts b/src/request.ts index 0489b473f..6df495e5f 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; +import { RequestError, ParameterValidationError } from './errors'; import { type Parameter, type DataType } from './data-type'; -import { RequestError } from './errors'; import Connection from './connection'; import { type Metadata } from './metadata-parser'; @@ -463,7 +463,11 @@ 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'); + 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; } } } diff --git a/test/integration/bulk-load-test.js b/test/integration/bulk-load-test.js index 107e238d6..185a72b66 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'; import { debugOptionsFromEnv } from '../helpers/debug-options-from-env'; @@ -1517,8 +1517,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(); } @@ -1537,8 +1539,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);