Skip to content

Commit

Permalink
fix: bullet proof APIX response handling
Browse files Browse the repository at this point in the history
# Description

APIX now renders raw data if it cannot parse the response returned as expected.

 # Changes

1. Added try/catches in appropriate places. Note that the try/catch in RunIt is probably NOT necessary but the code being wrapped indicates that an error could be thrown (logically it should never happen as the user should always be authenticated).
2. Renamed `parseJson` method to `json2Csv` to better reflect what it does (I was initially confused by the original method name).
3. Created a ShowRaw response handler.
4. Created a fallbackResponseHandler to use ShowRaw.
  • Loading branch information
bryans99 committed Jul 14, 2021
1 parent 3321bf1 commit 03694c8
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 23 deletions.
18 changes: 15 additions & 3 deletions packages/run-it/src/RunIt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,9 @@ export const RunIt: FC<RunItProps> = ({
tabs.onSelectTab(1)
if (sdk) {
setLoading(true)
setResponseContent(
await runRequest(
let response: ResponseContent
try {
response = await runRequest(
sdk,
basePath,
httpMethod,
Expand All @@ -178,7 +179,18 @@ export const RunIt: FC<RunItProps> = ({
queryParams,
body
)
)
} catch (err) {
// This should not happen but it could. runRequest uses
// sdk.ok to login once. sdk.ok throws an error so fake
// out the response so that something can be rendered.
response = {
ok: false,
statusMessage: err.message ? err.message : 'Unknown error!',
statusCode: -1,
body: JSON.stringify(err),
} as ResponseContent
}
setResponseContent(response)
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/run-it/src/components/DataGrid/gridUtils.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/

import { gridHeaders, gridRows, parseCsv, parseJson } from './gridUtils'
import { gridHeaders, gridRows, parseCsv, json2Csv } from './gridUtils'

const dataRowsLength = 2
const allRowsLength = dataRowsLength + 1
Expand Down Expand Up @@ -59,7 +59,7 @@ describe('gridUtils', () => {
})

test('parses json data', () => {
const actual = parseJson(testJsonData)
const actual = json2Csv(testJsonData)
expect(actual).toBeDefined()
expect(actual.data).toBeDefined()
expect(actual.data).toHaveLength(allRowsLength)
Expand All @@ -80,7 +80,7 @@ describe('gridUtils', () => {
})

test('creates grid rows from json', () => {
const data = parseJson(testJsonData)
const data = json2Csv(testJsonData)
const actual = gridRows(data.data)
expect(actual).toHaveLength(allRowsLength)
})
Expand Down
2 changes: 1 addition & 1 deletion packages/run-it/src/components/DataGrid/gridUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const parseCsv = (content: string) => {
return Papa.parse(content.trim())
}

export const parseJson = (content: any) => {
export const json2Csv = (content: any) => {
const csv = Papa.unparse(content)
return parseCsv(csv)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/run-it/src/components/DataGrid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
*/
export { DataGrid } from './DataGrid'
export { parseCsv, parseJson } from './gridUtils'
export { parseCsv, json2Csv } from './gridUtils'
12 changes: 12 additions & 0 deletions packages/run-it/src/components/ShowResponse/ShowResponse.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
testJsonResponse,
testTextResponse,
testUnknownResponse,
testBogusJsonResponse,
} from '../../test-data'
import { ShowResponse } from './ShowResponse'

Expand Down Expand Up @@ -97,4 +98,15 @@ describe('ShowResponse', () => {
screen.getByText(testErrorResponse.body.toString(), { exact: false })
).toBeInTheDocument()
})

test('it renders bogus json responses', () => {
renderWithTheme(<ShowResponse response={testBogusJsonResponse} />)
expect(screen.getByText('200: application/json')).toBeInTheDocument()
expect(
screen.getByText(
'The response body could not be parsed. Displaying raw data.'
)
).toBeInTheDocument()
expect(screen.getByText('I AM A LYING JSON RESPONSE')).toBeInTheDocument()
})
})
13 changes: 10 additions & 3 deletions packages/run-it/src/components/ShowResponse/ShowResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import React, { FC } from 'react'
import { Heading } from '@looker/components'
import { IRawResponse } from '@looker/sdk-rtl'

import { pickResponseHandler } from './responseUtils'
import { pickResponseHandler, fallbackResponseHandler } from './responseUtils'

interface ShowResponseProps {
/** A basic HTTP response for "raw" HTTP requests */
Expand All @@ -47,7 +47,14 @@ export const ShowResponse: FC<ShowResponseProps> = ({
verb,
path,
}) => {
const pickedHandler = pickResponseHandler(response)
// Bullet proof the rendered response. If for some reason we get a bad response or bad data in the
// response, render something
let renderedResponse
try {
renderedResponse = pickResponseHandler(response).component(response)
} catch (err) {
renderedResponse = fallbackResponseHandler().component(response)
}

// TODO make a badge for the verb.
// Once we are satisfied with the badge in the api-explorer package it should be moved here
Expand All @@ -56,7 +63,7 @@ export const ShowResponse: FC<ShowResponseProps> = ({
<Heading as="h4">{`${verb || ''} ${path || ''} ${response.statusCode}: ${
response.contentType
}`}</Heading>
{pickedHandler && pickedHandler.component(response)}
{renderedResponse}
</>
)
}
41 changes: 29 additions & 12 deletions packages/run-it/src/components/ShowResponse/responseUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
*/
import React, { ReactElement } from 'react'
import { IRawResponse, ResponseMode, responseMode } from '@looker/sdk-rtl'
import { Paragraph, CodeBlock } from '@looker/components'
import { Paragraph, CodeBlock, MessageBar } from '@looker/components'
import { CodeDisplay } from '@looker/code-editor'

import { DataGrid, parseCsv, parseJson } from '../DataGrid'
import { DataGrid, parseCsv, json2Csv } from '../DataGrid'

/**
* Are all items this array "simple"
Expand Down Expand Up @@ -63,21 +63,17 @@ export const isColumnar = (data: any[]) => {
*
* Shows the JSON in a syntax-highlighted fashion
* If the JSON is parseable as 2D row/column data it will also be shown in grid
* If JSON cannot be parsed it will be show as is
* @param response
*/
const ShowJSON = (response: IRawResponse) => {
const content = response.body.toString()
const data = parseJson(content)
const data = json2Csv(content)
const showGrid = isColumnar(data.data)
const raw = (
<CodeDisplay
code={JSON.stringify(JSON.parse(response.body), null, 2)}
lineNumbers={false}
transparent
/>
)
if (!showGrid) return raw
return <DataGrid data={data.data} raw={raw} />
const json = JSON.stringify(JSON.parse(response.body), null, 2)
const raw = <CodeDisplay code={json} lineNumbers={false} transparent />
if (showGrid) return <DataGrid data={data.data} raw={raw} />
return raw
}

/** A handler for text type responses */
Expand Down Expand Up @@ -141,6 +137,21 @@ const ShowPDF = (response: IRawResponse) => {
return ShowUnknown(response)
}

/** A handler for responses that cannot be parsed */
const ShowRaw = (response: IRawResponse) => (
<>
{ShowUnknown(response)}
<MessageBar intent="warn" noActions>
The response body could not be parsed. Displaying raw data.
</MessageBar>
<CodeDisplay
language="unknown"
code={response.body.toString()}
transparent
/>
</>
)

interface Responder {
/** A label indicating the supported MIME type(s) */
label: string
Expand Down Expand Up @@ -213,3 +224,9 @@ export const pickResponseHandler = (response: IRawResponse) => {
}
return result
}

export const fallbackResponseHandler = (): Responder => ({
label: 'unknown',
isRecognized: (contentType: string) => !!contentType,
component: (response) => ShowRaw(response),
})
1 change: 1 addition & 0 deletions packages/run-it/src/test-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export {
testHtmlResponse,
testUnknownResponse,
testErrorResponse,
testBogusJsonResponse,
} from './responses'
export { api } from './specs'
10 changes: 10 additions & 0 deletions packages/run-it/src/test-data/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,13 @@ export const testErrorResponse: IRawResponse = {
statusCode: 404,
statusMessage: 'some status message',
}

export const testBogusJsonResponse: IRawResponse = {
url: 'https://some/json/data',
headers: {},
contentType: 'application/json',
ok: true,
statusCode: 200,
statusMessage: 'OK',
body: Buffer.from('<html><body>I AM A LYING JSON RESPONSE</body></html>'),
}

0 comments on commit 03694c8

Please sign in to comment.