diff --git a/src/SASjs.ts b/src/SASjs.ts index d88ee4c..5a4a5c2 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -1170,8 +1170,8 @@ export default class SASjs { * @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**. */ public enableVerboseMode( - successCallBack?: (response: AxiosResponse | AxiosError) => AxiosResponse, - errorCallBack?: (response: AxiosResponse | AxiosError) => AxiosResponse + successCallBack?: (response: AxiosResponse) => AxiosResponse, + errorCallBack?: (response: AxiosError) => AxiosError ) { this.requestClient?.enableVerboseMode(successCallBack, errorCallBack) } diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index d2efa61..81b181a 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -1,5 +1,6 @@ import { AxiosError, + AxiosHeaders, AxiosInstance, AxiosRequestConfig, AxiosResponse @@ -413,103 +414,85 @@ export class RequestClient implements HttpClient { return bodyLines.join('\n') } - private defaultInterceptionCallBack = ( - axiosResponse: AxiosResponse | AxiosError - ) => { + private handleAxiosResponse = (response: AxiosResponse) => { + const { status, config, request, data } = response + + const reqHeaders = request?._header ?? 'Not provided\n' + const rawHeaders = request?.res?.rawHeaders ?? ['Not provided'] + + const resHeaders = this.formatHeaders(rawHeaders) + const parsedResBody = this.parseInterceptedBody(data) + + process.logger?.info(`HTTP Request (first 50 lines): + ${reqHeaders}${this.parseInterceptedBody(config.data)} + + HTTP Response Code: ${this.prettifyString(status)} + + HTTP Response (first 50 lines): + ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''} + `) + + return response + } + + private handleAxiosError = (error: AxiosError) => { // Message indicating absent value. const noValueMessage = 'Not provided' + const { response, request, config } = error // Fallback request object that can be safely used to form request summary. - type FallbackRequest = { _header?: string; res: { rawHeaders: string[] } } // _header is not present in responses with status 1** // rawHeaders are not present in responses with status 1** - let fallbackRequest: FallbackRequest = { - _header: `${noValueMessage}\n`, + let fallbackRequest = { + _header: noValueMessage, res: { rawHeaders: [noValueMessage] } } - // Fallback response object that can be safely used to form response summary. - type FallbackResponse = { - status?: number | string - request?: FallbackRequest - config: { data?: string } - data?: unknown - } - let fallbackResponse: FallbackResponse = axiosResponse - - if (axios.isAxiosError(axiosResponse)) { - const { response, request, config } = axiosResponse - - // Try to use axiosResponse.response to form response summary. - if (response) { - fallbackResponse = response - } else { - // Try to use axiosResponse.request to form request summary. - if (request) { - const { _header, _currentRequest } = request - - // Try to use axiosResponse.request._header to form request summary. - if (_header) { - fallbackRequest._header = _header - } - // Try to use axiosResponse.request._currentRequest._header to form request summary. - else if (_currentRequest && _currentRequest._header) { - fallbackRequest._header = _currentRequest._header - } - - const { res } = request - - // Try to use axiosResponse.request.res.rawHeaders to form request summary. - if (res && res.rawHeaders) { - fallbackRequest.res.rawHeaders = res.rawHeaders - } - } - - // Fallback config that can be safely used to form response summary. - const fallbackConfig = { data: noValueMessage } - - fallbackResponse = { - status: noValueMessage, - request: fallbackRequest, - config: config || fallbackConfig, - data: noValueMessage - } + if (request) { + fallbackRequest = { + _header: + request._header ?? request._currentRequest?._header ?? noValueMessage, + res: { rawHeaders: request.res?.rawHeaders ?? [noValueMessage] } } } - const { status, config, request, data: resData } = fallbackResponse - const { data: reqData } = config - const { _header: reqHeaders, res } = request || fallbackRequest - const { rawHeaders } = res + // Fallback response object that can be safely used to form response summary. + let fallbackResponse = response || { + status: noValueMessage, + request: fallbackRequest, + config: config || { data: noValueMessage, headers: new AxiosHeaders() }, + data: noValueMessage + } - // Converts an array of strings into a single string with the following format: - // : - const resHeaders = rawHeaders.reduce( - (acc: string, value: string, i: number) => { - if (i % 2 === 0) { - acc += `${i === 0 ? '' : '\n'}${value}` - } else { - acc += `: ${value}` - } - - return acc - }, - '' - ) + const { status, request: req, data: resData } = fallbackResponse + const { _header: reqHeaders, res } = req + const resHeaders = this.formatHeaders(res.rawHeaders) const parsedResBody = this.parseInterceptedBody(resData) - // HTTP response summary. process.logger?.info(`HTTP Request (first 50 lines): -${reqHeaders}${this.parseInterceptedBody(reqData)} + ${reqHeaders}${this.parseInterceptedBody(config?.data)} + + HTTP Response Code: ${this.prettifyString(status)} + + HTTP Response (first 50 lines): + ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''} + `) -HTTP Response Code: ${this.prettifyString(status)} + return error + } -HTTP Response (first 50 lines): -${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''} -`) - - return axiosResponse + // Converts an array of strings into a single string with the following format: + // : + private formatHeaders = (rawHeaders: string[]): string => { + return rawHeaders.reduce((acc, value, i) => { + if (i % 2 === 0) { + acc += `${i === 0 ? '' : '\n'}${value}` + } else { + acc += `: ${value}` + } + return acc + }, '') } /** @@ -529,8 +512,8 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''} * @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**. */ public enableVerboseMode = ( - successCallBack = this.defaultInterceptionCallBack, - errorCallBack = this.defaultInterceptionCallBack + successCallBack = this.handleAxiosResponse, + errorCallBack = this.handleAxiosError ) => { this.httpInterceptor = this.httpClient.interceptors.response.use( successCallBack, diff --git a/src/test/RequestClient.spec.ts b/src/test/RequestClient.spec.ts index 7bcb521..2618618 100644 --- a/src/test/RequestClient.spec.ts +++ b/src/test/RequestClient.spec.ts @@ -75,7 +75,7 @@ describe('RequestClient', () => { expect(rejectionErrorMessage).toEqual(expectedError.message) }) - describe('defaultInterceptionCallBack', () => { + describe('defaultInterceptionCallBacks for successful requests and failed requests', () => { const reqHeaders = `POST https://sas.server.com/compute/sessions/session_id/jobs HTTP/1.1 Accept: application/json Content-Type: application/json @@ -181,7 +181,7 @@ Connection: close } as AxiosError const requestClient = new RequestClient('') - requestClient['defaultInterceptionCallBack'](mockedAxiosError) + requestClient['handleAxiosError'](mockedAxiosError) const noValueMessage = 'Not provided' const expectedLog = `HTTP Request (first 50 lines): @@ -214,7 +214,7 @@ ${noValueMessage} } const requestClient = new RequestClient('') - requestClient['defaultInterceptionCallBack'](mockedResponse) + requestClient['handleAxiosResponse'](mockedResponse) const expectedLog = `HTTP Request (first 50 lines): ${reqHeaders}${requestClient['parseInterceptedBody'](reqData)} @@ -235,29 +235,29 @@ ${resHeaders[0]}: ${resHeaders[1]}${ it('should log parsed response with status 3**', () => { const status = getRandomStatus([300, 301, 302, 303, 304, 307, 308]) - const mockedResponse: AxiosResponse = { - data: resData, - status, - statusText: '', - headers: {}, - config: { data: reqData }, - request: { _header: reqHeaders, res: { rawHeaders: resHeaders } } - } + const mockedAxiosError = { + config: { + data: reqData + }, + request: { + _currentRequest: { + _header: reqHeaders + } + } + } as AxiosError const requestClient = new RequestClient('') - requestClient['defaultInterceptionCallBack'](mockedResponse) + requestClient['handleAxiosError'](mockedAxiosError) + const noValueMessage = 'Not provided' const expectedLog = `HTTP Request (first 50 lines): ${reqHeaders}${requestClient['parseInterceptedBody'](reqData)} -HTTP Response Code: ${requestClient['prettifyString'](status)} +HTTP Response Code: ${requestClient['prettifyString'](noValueMessage)} HTTP Response (first 50 lines): -${resHeaders[0]}: ${resHeaders[1]}${ - requestClient['parseInterceptedBody'](resData) - ? `\n\n${requestClient['parseInterceptedBody'](resData)}` - : '' - } +${noValueMessage} +\n${requestClient['parseInterceptedBody'](noValueMessage)} ` expect((process as any).logger.info).toHaveBeenCalledWith(expectedLog) @@ -294,7 +294,7 @@ ${resHeaders[0]}: ${resHeaders[1]}${ } as AxiosError const requestClient = new RequestClient('') - requestClient['defaultInterceptionCallBack'](mockedAxiosError) + requestClient['handleAxiosError'](mockedAxiosError) const expectedLog = `HTTP Request (first 50 lines): ${reqHeaders}${requestClient['parseInterceptedBody'](reqData)} @@ -344,7 +344,7 @@ ${resHeaders[0]}: ${resHeaders[1]}${ } as AxiosError const requestClient = new RequestClient('') - requestClient['defaultInterceptionCallBack'](mockedAxiosError) + requestClient['handleAxiosError'](mockedAxiosError) const expectedLog = `HTTP Request (first 50 lines): ${reqHeaders}${requestClient['parseInterceptedBody'](reqData)} @@ -376,8 +376,8 @@ ${resHeaders[0]}: ${resHeaders[1]}${ requestClient.enableVerboseMode() expect(interceptorSpy).toHaveBeenCalledWith( - requestClient['defaultInterceptionCallBack'], - requestClient['defaultInterceptionCallBack'] + requestClient['handleAxiosResponse'], + requestClient['handleAxiosError'] ) }) @@ -388,12 +388,12 @@ ${resHeaders[0]}: ${resHeaders[1]}${ 'use' ) - const successCallback = (response: AxiosResponse | AxiosError) => { + const successCallback = (response: AxiosResponse) => { console.log('success') return response } - const failureCallback = (response: AxiosResponse | AxiosError) => { + const failureCallback = (response: AxiosError) => { console.log('failure') return response