diff --git a/src/FileUploader.ts b/src/FileUploader.ts index 7001bcb..38d2f52 100644 --- a/src/FileUploader.ts +++ b/src/FileUploader.ts @@ -61,24 +61,21 @@ export class FileUploader { 'Content-Type': 'text/plain' } + // currently only web approach is supported for file upload + // therefore log is part of response with debug enabled and must be parsed return this.requestClient .post(uploadUrl, formData, undefined, 'application/json', headers) .then(async (res) => { - // for web approach on Viya if ( - this.sasjsConfig.debug && - (this.sasjsConfig.useComputeApi === null || - this.sasjsConfig.useComputeApi === undefined) && - this.sasjsConfig.serverType === ServerType.SasViya + this.sasjsConfig.serverType === ServerType.SasViya && + this.sasjsConfig.debug ) { const jsonResponse = await parseSasViyaDebugResponse( res.result as string, this.requestClient, this.sasjsConfig.serverUrl ) - return typeof jsonResponse === 'string' - ? getValidJson(jsonResponse) - : jsonResponse + return jsonResponse } return typeof res.result === 'string' diff --git a/src/SASjs.ts b/src/SASjs.ts index 015007b..5924985 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -619,6 +619,11 @@ export default class SASjs { authConfig ) } else { + if (!config.contextName) + config = { + ...config, + contextName: 'SAS Job Execution compute context' + } return await this.jesJobExecutor!.execute( sasJob, data, diff --git a/src/auth/AuthManager.ts b/src/auth/AuthManager.ts index 33e9b46..ed91d30 100644 --- a/src/auth/AuthManager.ts +++ b/src/auth/AuthManager.ts @@ -128,7 +128,8 @@ export class AuthManager { if (!isLoggedIn) { //We will logout to make sure cookies are removed and login form is presented - this.logOut() + //Residue can happen in case of session expiration + await this.logOut() const { result: formResponse } = await this.requestClient.get( this.loginUrl.replace('.do', ''), diff --git a/src/job-execution/WebJobExecutor.ts b/src/job-execution/WebJobExecutor.ts index 98063fa..d3b6faf 100644 --- a/src/job-execution/WebJobExecutor.ts +++ b/src/job-execution/WebJobExecutor.ts @@ -2,7 +2,8 @@ import { ServerType } from '@sasjs/utils/types' import { ErrorResponse, JobExecutionError, - LoginRequiredError + LoginRequiredError, + WeboutResponseError } from '../types/errors' import { generateFileUploadForm } from '../file/generateFileUploadForm' import { generateTableUploadForm } from '../file/generateTableUploadForm' @@ -54,7 +55,21 @@ export class WebJobExecutor extends BaseJobExecutor { apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : '' - apiUrl += config.contextName ? `&_contextname=${config.contextName}` : '' + if (jobUri.length > 0) { + apiUrl += '&_job=' + jobUri + /** + * Using both _job and _program parameters will cause a conflict in the JES web app, as it’s not clear whether or not the server should make the extra fetch for the job uri. + * To handle this, we add the extra underscore and recreate the _program variable in the SAS side of the SASjs adapter so it remains available for backend developers. + */ + apiUrl = apiUrl.replace('_program=', '__program=') + } + + // if context name exists and is not blank string + // then add _contextname variable in apiUrl + apiUrl += + config.contextName && !/\s/.test(config.contextName) + ? `&_contextname=${config.contextName}` + : '' } let requestParams = { @@ -97,10 +112,10 @@ export class WebJobExecutor extends BaseJobExecutor { const requestPromise = new Promise((resolve, reject) => { this.requestClient!.post(apiUrl, formData, undefined) - .then(async (res) => { + .then(async (res: any) => { if (this.serverType === ServerType.SasViya && config.debug) { const jsonResponse = await parseSasViyaDebugResponse( - res.result as string, + res.result, this.requestClient, this.serverUrl ) @@ -108,19 +123,16 @@ export class WebJobExecutor extends BaseJobExecutor { resolve(jsonResponse) } if (this.serverType === ServerType.Sas9 && config.debug) { - const jsonResponse = parseWeboutResponse(res.result as string) - if (jsonResponse === '') { - throw new Error( - 'Valid JSON could not be extracted from response.' - ) - } + let jsonResponse = res.result + if (typeof res.result === 'string') + jsonResponse = parseWeboutResponse(res.result, apiUrl) getValidJson(jsonResponse) this.appendRequest(res, sasJob, config.debug) resolve(res.result) } - getValidJson(res.result as string) this.appendRequest(res, sasJob, config.debug) + getValidJson(res.result as string) resolve(res.result) }) .catch(async (e: Error) => { diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index e44a8c4..28c5512 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -429,13 +429,7 @@ export class RequestClient implements HttpClient { } } catch { try { - const weboutResponse = parseWeboutResponse(response.data) - if (weboutResponse === '') { - throw new Error('Valid JSON could not be extracted from response.') - } - - const jsonResponse = getValidJson(weboutResponse) - parsedResponse = jsonResponse + parsedResponse = JSON.parse(parseWeboutResponse(response.data)) } catch { parsedResponse = response.data } diff --git a/src/test/utils/getValidJson.spec.ts b/src/test/utils/getValidJson.spec.ts index e7fbf66..a84eb04 100644 --- a/src/test/utils/getValidJson.spec.ts +++ b/src/test/utils/getValidJson.spec.ts @@ -1,4 +1,5 @@ import { getValidJson } from '../../utils' +import { JsonParseArrayError, InvalidJsonError } from '../../types/errors' describe('jsonValidator', () => { it('should not throw an error with a valid json', () => { @@ -19,23 +20,17 @@ describe('jsonValidator', () => { it('should throw an error with an invalid json', () => { const json = `{\"test\":\"test\"\"test2\":\"test\"}` - let errorThrown = false - try { + const test = () => { getValidJson(json) - } catch (error) { - errorThrown = true } - expect(errorThrown).toBe(true) + expect(test).toThrowError(InvalidJsonError) }) it('should throw an error when an array is passed', () => { const array = ['hello', 'world'] - let errorThrown = false - try { + const test = () => { getValidJson(array) - } catch (error) { - errorThrown = true } - expect(errorThrown).toBe(true) + expect(test).toThrow(JsonParseArrayError) }) }) diff --git a/src/types/errors/InvalidJsonError.ts b/src/types/errors/InvalidJsonError.ts new file mode 100644 index 0000000..f59339a --- /dev/null +++ b/src/types/errors/InvalidJsonError.ts @@ -0,0 +1,7 @@ +export class InvalidJsonError extends Error { + constructor() { + super('Error: invalid Json string') + this.name = 'InvalidJsonError' + Object.setPrototypeOf(this, InvalidJsonError.prototype) + } +} diff --git a/src/types/errors/JsonParseArrayError.ts b/src/types/errors/JsonParseArrayError.ts new file mode 100644 index 0000000..ad08ecd --- /dev/null +++ b/src/types/errors/JsonParseArrayError.ts @@ -0,0 +1,7 @@ +export class JsonParseArrayError extends Error { + constructor() { + super('Can not parse array object to json.') + this.name = 'JsonParseArrayError' + Object.setPrototypeOf(this, JsonParseArrayError.prototype) + } +} diff --git a/src/types/errors/WeboutResponseError.ts b/src/types/errors/WeboutResponseError.ts new file mode 100644 index 0000000..9ca1e80 --- /dev/null +++ b/src/types/errors/WeboutResponseError.ts @@ -0,0 +1,7 @@ +export class WeboutResponseError extends Error { + constructor(public url: string) { + super(`Error: error while parsing response from ${url}`) + this.name = 'WeboutResponseError' + Object.setPrototypeOf(this, WeboutResponseError.prototype) + } +} diff --git a/src/types/errors/index.ts b/src/types/errors/index.ts index cca1a97..e2b0766 100644 --- a/src/types/errors/index.ts +++ b/src/types/errors/index.ts @@ -8,3 +8,6 @@ export * from './NotFoundError' export * from './ErrorResponse' export * from './NoSessionStateError' export * from './RootFolderNotFoundError' +export * from './JsonParseArrayError' +export * from './WeboutResponseError' +export * from './InvalidJsonError' diff --git a/src/utils/getValidJson.ts b/src/utils/getValidJson.ts index 0313157..17f9cbb 100644 --- a/src/utils/getValidJson.ts +++ b/src/utils/getValidJson.ts @@ -1,16 +1,18 @@ +import { JsonParseArrayError, InvalidJsonError } from '../types/errors' + /** * if string passed then parse the string to json else if throw error for all other types unless it is not a valid json object. * @param str - string to check. */ export const getValidJson = (str: string | object) => { try { - if (Array.isArray(str)) { - throw new Error('Can not parse array object to json.') - } + if (Array.isArray(str)) throw new JsonParseArrayError() + if (typeof str === 'object') return str return JSON.parse(str) } catch (e) { - throw new Error('Invalid JSON response.') + if (e instanceof JsonParseArrayError) throw e + throw new InvalidJsonError() } } diff --git a/src/utils/parseViyaDebugResponse.ts b/src/utils/parseViyaDebugResponse.ts index 3137995..b0227f2 100644 --- a/src/utils/parseViyaDebugResponse.ts +++ b/src/utils/parseViyaDebugResponse.ts @@ -1,4 +1,5 @@ import { RequestClient } from '../request/RequestClient' +import { getValidJson } from '../utils' /** * When querying a Viya job using the Web approach (as opposed to using the APIs) with _DEBUG enabled, @@ -25,5 +26,5 @@ export const parseSasViyaDebugResponse = async ( return requestClient .get(serverUrl + jsonUrl, undefined) - .then((res) => res.result) + .then((res: any) => getValidJson(res.result)) } diff --git a/src/utils/parseWeboutResponse.ts b/src/utils/parseWeboutResponse.ts index f9bc4db..30d91b6 100644 --- a/src/utils/parseWeboutResponse.ts +++ b/src/utils/parseWeboutResponse.ts @@ -1,4 +1,6 @@ -export const parseWeboutResponse = (response: string) => { +import { WeboutResponseError } from '../types/errors' + +export const parseWeboutResponse = (response: string, url?: string) => { let sasResponse = '' if (response.includes('>>weboutBEGIN<<')) { @@ -7,6 +9,7 @@ export const parseWeboutResponse = (response: string) => { .split('>>weboutBEGIN<<')[1] .split('>>weboutEND<<')[0] } catch (e) { + if (url) throw new WeboutResponseError(url) sasResponse = '' console.error(e) }