From 38c3849ef352d03735cb92c7a7afba8f38da5fa2 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Tue, 30 Apr 2024 16:00:39 +0300 Subject: [PATCH] Fix: malformed json error (#3107) * create json validator function * make message more readable * show response alongside warning --- .../actions/query-action-creator-util.ts | 10 ++- .../services/actions/query-action-creators.ts | 4 +- .../query-response/response/Response.tsx | 15 ++-- .../response/ResponseMessages.tsx | 78 ++++++++++++------- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/app/services/actions/query-action-creator-util.ts b/src/app/services/actions/query-action-creator-util.ts index 6b60a2d150..fe7cf328a6 100644 --- a/src/app/services/actions/query-action-creator-util.ts +++ b/src/app/services/actions/query-action-creator-util.ts @@ -221,6 +221,14 @@ export async function generateResponseDownloadUrl( } } +async function tryParseJson(textValue: string) { + try { + return JSON.parse(textValue); + } catch (error) { + return textValue; + } +} + export function parseResponse( response: any, respHeaders: any = {} @@ -233,7 +241,7 @@ export function parseResponse( const contentType = getContentType(response.headers); switch (contentType) { case ContentType.Json: - return response.json(); + return response.text().then(tryParseJson); case ContentType.XML: case ContentType.HTML: case ContentType.TextCsv: diff --git a/src/app/services/actions/query-action-creators.ts b/src/app/services/actions/query-action-creators.ts index 1701be8df9..d0bc91a38f 100644 --- a/src/app/services/actions/query-action-creators.ts +++ b/src/app/services/actions/query-action-creators.ts @@ -145,8 +145,8 @@ export function runQuery(query: IQuery) { }; if (error instanceof ClientError) { - status.status = 0; - status.statusText = `${error.name}: ${error.message}`; + status.status = error.message; + status.statusText = error.name; } if (queryResultsInCorsError(query.sampleUrl)) { diff --git a/src/app/views/query-response/response/Response.tsx b/src/app/views/query-response/response/Response.tsx index eed57fb7f8..4c8fa7896c 100644 --- a/src/app/views/query-response/response/Response.tsx +++ b/src/app/views/query-response/response/Response.tsx @@ -1,18 +1,18 @@ -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppSelector } from '../../../../store'; import { getContentType } from '../../../services/actions/query-action-creator-util'; -import { convertVhToPx, getResponseEditorHeight, - getResponseHeight } from '../../common/dimensions/dimensions-adjustment'; +import { + convertVhToPx, getResponseEditorHeight, + getResponseHeight +} from '../../common/dimensions/dimensions-adjustment'; import ResponseDisplay from './ResponseDisplay'; import { ResponseMessages } from './ResponseMessages'; const Response = () => { - const { dimensions: { response }, graphResponse, responseAreaExpanded, sampleQuery, authToken, graphExplorerMode } = + const { dimensions: { response }, graphResponse, responseAreaExpanded} = useAppSelector((state) => state); const { body, headers } = graphResponse; - const dispatch: AppDispatch = useDispatch(); const defaultHeight = convertVhToPx(getResponseHeight(response.height, responseAreaExpanded), 220); const monacoHeight = getResponseEditorHeight(150); @@ -20,9 +20,10 @@ const Response = () => { const contentDownloadUrl = body?.contentDownloadUrl; const throwsCorsError = body?.throwsCorsError; const contentType = getContentType(headers); + return (
- {ResponseMessages(graphResponse, sampleQuery, authToken, graphExplorerMode, dispatch)} + {!contentDownloadUrl && !throwsCorsError && headers && { - if (responseBody[`@odata.${link}`]) { - data = { - link: responseBody[`@odata.${link}`], - name: link - }; - } - }); - } - return data; +function getOdataLinkFromResponseBody(responseBody: any): ODataLink | null { + const odataLinks = ['nextLink', 'deltaLink']; + let data = null; + if (responseBody) { + odataLinks.forEach(link => { + if (responseBody[`@odata.${link}`]) { + data = { + link: responseBody[`@odata.${link}`], + name: link + }; + } + }); } + return data; +} + +export const ResponseMessages = () => { + const dispatch: AppDispatch = useDispatch(); + const messageBars = []; + + const { graphResponse: { body, headers }, sampleQuery, authToken, graphExplorerMode + } = useAppSelector((state) => state); const [displayMessage, setDisplayMessage] = useState(true); + const tokenPresent = !!authToken.token; - const { body } = graphResponse; + const contentType = getContentType(headers); const odataLink = getOdataLinkFromResponseBody(body); const setQuery = () => { @@ -51,7 +53,7 @@ export function ResponseMessages( // Display link to step to next result if (odataLink) { - return ( + messageBars.push( {translateMessage('This response contains an @odata property.')}: @odata.{odataLink.name} setQuery()} underline> @@ -63,7 +65,7 @@ export function ResponseMessages( // Display link to download file response if (body?.contentDownloadUrl) { - return ( + messageBars.push(
{translateMessage('This response contains unviewable content')} @@ -77,7 +79,7 @@ export function ResponseMessages( // Show CORS compliance message if (body?.throwsCorsError) { - return ( + messageBars.push(
{translateMessage('Response content not available due to CORS policy')} @@ -90,7 +92,7 @@ export function ResponseMessages( } if (body && !tokenPresent && displayMessage && graphExplorerMode === Mode.Complete) { - return ( + messageBars.push(
); } -} + + if (contentType === 'application/json' && typeof body === 'string') { + messageBars.push( +
+ setDisplayMessage(false)} + dismissButtonAriaLabel={translateMessage('Close')} + > + {translateMessage('Malformed JSON body')} + +
+ ); + } + + return messageBars; +} \ No newline at end of file