diff --git a/sasjs-tests/src/testSuites/RequestData.ts b/sasjs-tests/src/testSuites/RequestData.ts index a2088f3..b4bf93e 100644 --- a/sasjs-tests/src/testSuites/RequestData.ts +++ b/sasjs-tests/src/testSuites/RequestData.ts @@ -176,11 +176,59 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({ name: 'sendObj', tests: [ { - title: 'Invalid column name', + title: 'Table name starts with numeric', description: 'Should throw an error', test: async () => { const invalidData: any = { - '1 invalid table': [{ col1: 42 }] + '1InvalidTable': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: 'Table name contains a space', + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + 'an invalidTable': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: 'Table name contains a special character', + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + 'anInvalidTable#': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: 'Table name exceeds max length of 32 characters', + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) + }, + assertion: (error: any) => + !!error && !!error.error && !!error.error.message + }, + { + title: "Invalid data object's structure", + description: 'Should throw an error', + test: async () => { + const invalidData: any = { + inData: [[{ data: 'value' }]] } return adapter.request('common/sendObj', invalidData).catch((e) => e) }, diff --git a/src/SASjs.ts b/src/SASjs.ts index bc232fe..9304a51 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -581,7 +581,7 @@ export default class SASjs { */ public async request( sasJob: string, - data: { [key: string]: any }, + data: { [key: string]: any } | null, config: { [key: string]: any } = {}, loginRequiredCallback?: () => any, accessToken?: string, @@ -592,17 +592,36 @@ export default class SASjs { ...config } - if (config.serverType === ServerType.SasViya && config.contextName) { - if (config.useComputeApi) { - return await this.computeJobExecutor!.execute( - sasJob, - data, - config, - loginRequiredCallback, - accessToken - ) + const validationResult = this.validateInput(data) + + if (validationResult.status) { + if (config.serverType === ServerType.SasViya && config.contextName) { + if (config.useComputeApi) { + return await this.computeJobExecutor!.execute( + sasJob, + data, + config, + loginRequiredCallback, + accessToken + ) + } else { + return await this.jesJobExecutor!.execute( + sasJob, + data, + config, + loginRequiredCallback, + accessToken, + extraResponseAttributes + ) + } + } else if ( + config.serverType === ServerType.Sas9 && + config.username && + config.password + ) { + return await this.sas9JobExecutor!.execute(sasJob, data, config) } else { - return await this.jesJobExecutor!.execute( + return await this.webJobExecutor!.execute( sasJob, data, config, @@ -611,19 +630,71 @@ export default class SASjs { extraResponseAttributes ) } - } else if ( - config.serverType === ServerType.Sas9 && - config.username && - config.password - ) { - return await this.sas9JobExecutor!.execute(sasJob, data, config) } else { - return await this.webJobExecutor!.execute( - sasJob, - data, - config, - loginRequiredCallback - ) + return Promise.reject(new ErrorResponse(validationResult.msg)) + } + } + + /** + * This function validates the input data structure and table naming convention + * + * @param data A json object that contains one or more tables, it can also be null + * @returns An object which contains two attributes: 1) status: boolean, 2) msg: string + */ + private validateInput(data: { [key: string]: any } | null): { + status: boolean + msg: string + } { + if (data === null) return { status: true, msg: '' } + for (const key in data) { + if (!key.match(/^[a-zA-Z_]/)) { + return { + status: false, + msg: 'First letter of table should be alphabet or underscore.' + } + } + + if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) { + return { status: false, msg: 'Table name should be alphanumeric.' } + } + + if (key.length > 32) { + return { + status: false, + msg: 'Maximum length for table name could be 32 characters.' + } + } + + if (this.getType(data[key]) !== 'Array') { + return { + status: false, + msg: 'Parameter data contains invalid table structure.' + } + } + + for (let i = 0; i < data[key].length; i++) { + if (this.getType(data[key][i]) !== 'object') { + return { + status: false, + msg: `Table ${key} contains invalid structure.` + } + } + } + } + return { status: true, msg: '' } + } + + /** + * this function returns the type of variable + * + * @param data it could be anything, like string, array, object etc. + * @returns a string which tells the type of input parameter + */ + private getType(data: any): string { + if (Array.isArray(data)) { + return 'Array' + } else { + return typeof data } }