Add text to the error message when JSON parse fails
This commit is contained in:
		@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'
 | 
				
			|||||||
import type { CommandLog } from '@src/lang/std/commandLog'
 | 
					import type { CommandLog } from '@src/lang/std/commandLog'
 | 
				
			||||||
import { engineCommandManager } from '@src/lib/singletons'
 | 
					import { engineCommandManager } from '@src/lib/singletons'
 | 
				
			||||||
import { reportRejection } from '@src/lib/trap'
 | 
					import { reportRejection } from '@src/lib/trap'
 | 
				
			||||||
 | 
					import { parseJson } from '@src/lib/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useEngineCommands(): [CommandLog[], () => void] {
 | 
					export function useEngineCommands(): [CommandLog[], () => void] {
 | 
				
			||||||
  const [engineCommands, setEngineCommands] = useState<CommandLog[]>(
 | 
					  const [engineCommands, setEngineCommands] = useState<CommandLog[]>(
 | 
				
			||||||
@ -84,7 +85,7 @@ export const EngineCommands = () => {
 | 
				
			|||||||
        data-testid="custom-cmd-send-button"
 | 
					        data-testid="custom-cmd-send-button"
 | 
				
			||||||
        onClick={() => {
 | 
					        onClick={() => {
 | 
				
			||||||
          engineCommandManager
 | 
					          engineCommandManager
 | 
				
			||||||
            .sendSceneCommand(JSON.parse(customCmd))
 | 
					            .sendSceneCommand(parseJson(customCmd))
 | 
				
			||||||
            .catch(reportRejection)
 | 
					            .catch(reportRejection)
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import path from 'node:path'
 | 
				
			|||||||
import { assertParse } from '@src/lang/wasm'
 | 
					import { assertParse } from '@src/lang/wasm'
 | 
				
			||||||
import { initPromise } from '@src/lang/wasmUtils'
 | 
					import { initPromise } from '@src/lang/wasmUtils'
 | 
				
			||||||
import { enginelessExecutor } from '@src/lib/testHelpers'
 | 
					import { enginelessExecutor } from '@src/lib/testHelpers'
 | 
				
			||||||
 | 
					import { parseJson } from '@src/lib/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The purpose of these tests is to act as a first line of defense
 | 
					// The purpose of these tests is to act as a first line of defense
 | 
				
			||||||
// if something gets real screwy with our KCL ecosystem.
 | 
					// if something gets real screwy with our KCL ecosystem.
 | 
				
			||||||
@ -27,7 +28,7 @@ const manifestJsonStr = await fs.readFile(
 | 
				
			|||||||
  path.resolve(DIR_KCL_SAMPLES, 'manifest.json'),
 | 
					  path.resolve(DIR_KCL_SAMPLES, 'manifest.json'),
 | 
				
			||||||
  'utf-8'
 | 
					  'utf-8'
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
const manifest = JSON.parse(manifestJsonStr)
 | 
					const manifest: KclSampleFile[] = parseJson(manifestJsonStr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
process.chdir(DIR_KCL_SAMPLES)
 | 
					process.chdir(DIR_KCL_SAMPLES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import {
 | 
				
			|||||||
  getThemeColorForEngine,
 | 
					  getThemeColorForEngine,
 | 
				
			||||||
} from '@src/lib/theme'
 | 
					} from '@src/lib/theme'
 | 
				
			||||||
import { reportRejection } from '@src/lib/trap'
 | 
					import { reportRejection } from '@src/lib/trap'
 | 
				
			||||||
import { binaryToUuid, uuidv4 } from '@src/lib/utils'
 | 
					import { binaryToUuid, parseJson, uuidv4 } from '@src/lib/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const pingIntervalMs = 1_000
 | 
					const pingIntervalMs = 1_000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -390,7 +390,7 @@ class EngineConnection extends EventTarget {
 | 
				
			|||||||
    this.websocket.addEventListener('open', this.onWebSocketOpen)
 | 
					    this.websocket.addEventListener('open', this.onWebSocketOpen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.websocket?.addEventListener('message', ((event: MessageEvent) => {
 | 
					    this.websocket?.addEventListener('message', ((event: MessageEvent) => {
 | 
				
			||||||
      const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
 | 
					      const message: Models['WebSocketResponse_type'] = parseJson(event.data)
 | 
				
			||||||
      const pending =
 | 
					      const pending =
 | 
				
			||||||
        this.engineCommandManager.pendingCommands[message.request_id || '']
 | 
					        this.engineCommandManager.pendingCommands[message.request_id || '']
 | 
				
			||||||
      if (!('resp' in message)) return
 | 
					      if (!('resp' in message)) return
 | 
				
			||||||
@ -862,7 +862,7 @@ class EngineConnection extends EventTarget {
 | 
				
			|||||||
          )
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          this.onDataChannelMessage = (event) => {
 | 
					          this.onDataChannelMessage = (event) => {
 | 
				
			||||||
            const result: UnreliableResponses = JSON.parse(event.data)
 | 
					            const result: UnreliableResponses = parseJson(event.data)
 | 
				
			||||||
            Object.values(
 | 
					            Object.values(
 | 
				
			||||||
              this.engineCommandManager.unreliableSubscriptions[result.type] ||
 | 
					              this.engineCommandManager.unreliableSubscriptions[result.type] ||
 | 
				
			||||||
                {}
 | 
					                {}
 | 
				
			||||||
@ -976,7 +976,7 @@ class EngineConnection extends EventTarget {
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const message: Models['WebSocketResponse_type'] = JSON.parse(
 | 
					          const message: Models['WebSocketResponse_type'] = parseJson(
 | 
				
			||||||
            event.data
 | 
					            event.data
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1564,7 +1564,7 @@ export class EngineCommandManager extends EventTarget {
 | 
				
			|||||||
          unreliableDataChannel.addEventListener(
 | 
					          unreliableDataChannel.addEventListener(
 | 
				
			||||||
            'message',
 | 
					            'message',
 | 
				
			||||||
            (event: MessageEvent) => {
 | 
					            (event: MessageEvent) => {
 | 
				
			||||||
              const result: UnreliableResponses = JSON.parse(event.data)
 | 
					              const result: UnreliableResponses = parseJson(event.data)
 | 
				
			||||||
              Object.values(
 | 
					              Object.values(
 | 
				
			||||||
                this.unreliableSubscriptions[result.type] || {}
 | 
					                this.unreliableSubscriptions[result.type] || {}
 | 
				
			||||||
              ).forEach(
 | 
					              ).forEach(
 | 
				
			||||||
@ -1608,7 +1608,7 @@ export class EngineCommandManager extends EventTarget {
 | 
				
			|||||||
            message.request_id = binaryToUuid(message.request_id)
 | 
					            message.request_id = binaryToUuid(message.request_id)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          message = JSON.parse(event.data)
 | 
					          message = parseJson(event.data)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (message === null) {
 | 
					        if (message === null) {
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,7 @@ import type { CoreDumpManager } from '@src/lib/coredump'
 | 
				
			|||||||
import openWindow from '@src/lib/openWindow'
 | 
					import openWindow from '@src/lib/openWindow'
 | 
				
			||||||
import { Reason, err } from '@src/lib/trap'
 | 
					import { Reason, err } from '@src/lib/trap'
 | 
				
			||||||
import type { DeepPartial } from '@src/lib/types'
 | 
					import type { DeepPartial } from '@src/lib/types'
 | 
				
			||||||
import { isArray } from '@src/lib/utils'
 | 
					import { isArray, parseJson } from '@src/lib/utils'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  base64_decode,
 | 
					  base64_decode,
 | 
				
			||||||
  change_kcl_settings,
 | 
					  change_kcl_settings,
 | 
				
			||||||
@ -219,7 +219,7 @@ export const parse = (code: string | Error): ParseResult | Error => {
 | 
				
			|||||||
  } catch (e: any) {
 | 
					  } catch (e: any) {
 | 
				
			||||||
    // throw e
 | 
					    // throw e
 | 
				
			||||||
    console.error(e.toString())
 | 
					    console.error(e.toString())
 | 
				
			||||||
    const parsed: RustKclError = JSON.parse(e.toString())
 | 
					    const parsed: RustKclError = parseJson(e.toString())
 | 
				
			||||||
    return new KCLError(
 | 
					    return new KCLError(
 | 
				
			||||||
      parsed.kind,
 | 
					      parsed.kind,
 | 
				
			||||||
      parsed.msg,
 | 
					      parsed.msg,
 | 
				
			||||||
@ -381,7 +381,7 @@ export function sketchFromKclValue(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const errFromErrWithOutputs = (e: any): KCLError => {
 | 
					export const errFromErrWithOutputs = (e: any): KCLError => {
 | 
				
			||||||
  const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
 | 
					  const parsed: KclErrorWithOutputs = parseJson(e.toString())
 | 
				
			||||||
  return new KCLError(
 | 
					  return new KCLError(
 | 
				
			||||||
    parsed.error.kind,
 | 
					    parsed.error.kind,
 | 
				
			||||||
    parsed.error.msg,
 | 
					    parsed.error.msg,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import type { ApiError_type } from '@kittycad/lib/dist/types/src/models'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import type { Selections } from '@src/lib/selections'
 | 
					import type { Selections } from '@src/lib/selections'
 | 
				
			||||||
import { engineCommandManager, kclManager } from '@src/lib/singletons'
 | 
					import { engineCommandManager, kclManager } from '@src/lib/singletons'
 | 
				
			||||||
import { uuidv4 } from '@src/lib/utils'
 | 
					import { parseJson, uuidv4 } from '@src/lib/utils'
 | 
				
			||||||
import type { CommandBarContext } from '@src/machines/commandBarMachine'
 | 
					import type { CommandBarContext } from '@src/machines/commandBarMachine'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
 | 
					export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
 | 
				
			||||||
@ -54,7 +54,7 @@ export function parseEngineErrorMessage(engineError: string) {
 | 
				
			|||||||
    return undefined
 | 
					    return undefined
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const errors = JSON.parse(parts[1]) as ApiError_type[]
 | 
					  const errors = parseJson<ApiError_type[]>(parts[1])
 | 
				
			||||||
  if (!errors[0]) {
 | 
					  if (!errors[0]) {
 | 
				
			||||||
    return undefined
 | 
					    return undefined
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ import type { DeepPartial } from '@src/lib/types'
 | 
				
			|||||||
import type { ModuleType } from '@src/lib/wasm_lib_wrapper'
 | 
					import type { ModuleType } from '@src/lib/wasm_lib_wrapper'
 | 
				
			||||||
import { getModule } from '@src/lib/wasm_lib_wrapper'
 | 
					import { getModule } from '@src/lib/wasm_lib_wrapper'
 | 
				
			||||||
import type { Models } from '@kittycad/lib/dist/types/src'
 | 
					import type { Models } from '@kittycad/lib/dist/types/src'
 | 
				
			||||||
 | 
					import { parseJson } from '@src/lib/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class RustContext {
 | 
					export default class RustContext {
 | 
				
			||||||
  private wasmInitFailed: boolean = true
 | 
					  private wasmInitFailed: boolean = true
 | 
				
			||||||
@ -140,7 +141,7 @@ export default class RustContext {
 | 
				
			|||||||
        JSON.stringify(settings)
 | 
					        JSON.stringify(settings)
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
      const parsed: RustKclError = JSON.parse(e.toString())
 | 
					      const parsed: RustKclError = parseJson(e.toString())
 | 
				
			||||||
      toast.error(parsed.msg, { id: toastId })
 | 
					      toast.error(parsed.msg, { id: toastId })
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -60,6 +60,26 @@ export function isNonNullable<T>(val: T): val is NonNullable<T> {
 | 
				
			|||||||
  return val !== null && val !== undefined
 | 
					  return val !== null && val !== undefined
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Same as JSON.parse() but if it fails, includes the string that was attempted
 | 
				
			||||||
 | 
					 * to be parsed in the error message.  This is useful since a lot of times this
 | 
				
			||||||
 | 
					 * is called on a string that isn't actually JSON, like another error message.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You can also use the type parameter to assert the expected type of the parsed
 | 
				
			||||||
 | 
					 * object.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function parseJson<T>(
 | 
				
			||||||
 | 
					  text: string,
 | 
				
			||||||
 | 
					  reviver?: (this: any, key: string, value: any) => any
 | 
				
			||||||
 | 
					): T {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return JSON.parse(text, reviver)
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    // eslint-disable-next-line suggest-no-throw/suggest-no-throw
 | 
				
			||||||
 | 
					    throw new Error(`Failed to parse JSON: ${text}`, { cause: e })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isOverlap(a: SourceRange, b: SourceRange) {
 | 
					export function isOverlap(a: SourceRange, b: SourceRange) {
 | 
				
			||||||
  const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
 | 
					  const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
 | 
				
			||||||
  const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
 | 
					  const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user