Don't break the fact that processStringSync returns a string.

This commit is contained in:
Sam Rose 2024-10-03 13:07:22 +01:00
parent fdbe633b02
commit df242cc2ad
No known key found for this signature in database
7 changed files with 64 additions and 24 deletions

View File

@ -66,6 +66,7 @@
let insertAtPos let insertAtPos
let targetMode = null let targetMode = null
let expressionResult let expressionResult
let expressionError
let evaluating = false let evaluating = false
$: useSnippets = allowSnippets && !$licensing.isFreePlan $: useSnippets = allowSnippets && !$licensing.isFreePlan
@ -142,10 +143,16 @@
} }
const debouncedEval = Utils.debounce((expression, context, snippets) => { const debouncedEval = Utils.debounce((expression, context, snippets) => {
expressionResult = processStringSync(expression || "", { try {
...context, expressionError = null
snippets, expressionResult = processStringSync(expression || "", {
}) ...context,
snippets,
})
} catch (err) {
expressionResult = null
expressionError = err
}
evaluating = false evaluating = false
}, 260) }, 260)
@ -370,6 +377,7 @@
{:else if sidePanel === SidePanels.Evaluation} {:else if sidePanel === SidePanels.Evaluation}
<EvaluationSidePanel <EvaluationSidePanel
{expressionResult} {expressionResult}
{expressionError}
{evaluating} {evaluating}
expression={editorValue} expression={editorValue}
/> />

View File

@ -5,32 +5,35 @@
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
export let expressionResult export let expressionResult
export let expressionError
export let evaluating = false export let evaluating = false
export let expression = null export let expression = null
$: error = expressionResult && expressionResult.error != null $: error = expressionError != null
$: empty = expression == null || expression?.trim() === "" $: empty = expression == null || expression?.trim() === ""
$: success = !error && !empty $: success = !error && !empty
$: highlightedResult = highlight(expressionResult) $: highlightedResult = highlight(expressionResult)
const highlight = result => { const formatError = err => {
if (result == null) { if (err.code === "USER_SCRIPT_ERROR") {
return err.userScriptError.toString()
}
return err.toString()
}
const highlight = json => {
if (json == null) {
return "" return ""
} }
let str // Attempt to parse and then stringify, in case this is valid result
if (result.error) { try {
str = result.error.toString() json = JSON.stringify(JSON.parse(json), null, 2)
} else { } catch (err) {
// Attempt to parse and then stringify, in case this is valid result // Ignore
try {
str = JSON.stringify(JSON.parse(result.result), null, 2)
} catch (err) {
// Ignore
}
} }
return formatHighlight(str, { return formatHighlight(json, {
keyColor: "#e06c75", keyColor: "#e06c75",
numberColor: "#e5c07b", numberColor: "#e5c07b",
stringColor: "#98c379", stringColor: "#98c379",
@ -80,6 +83,8 @@
<div class="body"> <div class="body">
{#if empty} {#if empty}
Your expression will be evaluated here Your expression will be evaluated here
{:else if error}
{formatError(expressionError)}
{:else} {:else}
<!-- eslint-disable-next-line svelte/no-at-html-tags--> <!-- eslint-disable-next-line svelte/no-at-html-tags-->
{@html highlightedResult} {@html highlightedResult}

View File

@ -11,6 +11,7 @@ import { iifeWrapper } from "@budibase/string-templates"
import environment from "../../environment" import environment from "../../environment"
class ExecutionTimeoutError extends Error { class ExecutionTimeoutError extends Error {
code = "ERR_SCRIPT_EXECUTION_TIMEOUT"
constructor(message: string) { constructor(message: string) {
super(message) super(message)
this.name = "ExecutionTimeoutError" this.name = "ExecutionTimeoutError"
@ -18,6 +19,7 @@ class ExecutionTimeoutError extends Error {
} }
class UserScriptError extends Error { class UserScriptError extends Error {
code = "USER_SCRIPT_ERROR"
constructor(readonly userScriptError: Error) { constructor(readonly userScriptError: Error) {
super( super(
`error while running user-supplied JavaScript: ${userScriptError.message}`, `error while running user-supplied JavaScript: ${userScriptError.message}`,

View File

@ -81,7 +81,14 @@ export async function processFormulas<T extends Row | Row[]>(
...row, ...row,
[column]: tracer.trace("processStringSync", {}, span => { [column]: tracer.trace("processStringSync", {}, span => {
span?.addTags({ table_id: table._id, column, static: isStatic }) span?.addTags({ table_id: table._id, column, static: isStatic })
return processStringSync(formula, context) try {
return processStringSync(formula, context)
} catch (err: any) {
if (err.code === "USER_SCRIPT_ERROR") {
return err.userScriptError.toString()
}
throw err
}
}), }),
} }
} }

View File

@ -1,3 +1,13 @@
export class JsErrorTimeout extends Error { export class JsErrorTimeout extends Error {
code = "ERR_SCRIPT_EXECUTION_TIMEOUT" code = "ERR_SCRIPT_EXECUTION_TIMEOUT"
} }
export class UserScriptError extends Error {
code = "USER_SCRIPT_ERROR"
constructor(readonly userScriptError: Error) {
super(
`error while running user-supplied JavaScript: ${userScriptError.message}`
)
}
}

View File

@ -100,8 +100,8 @@ export function processJS(handlebars: string, context: any) {
if (error.name === "ExecutionTimeoutError") { if (error.name === "ExecutionTimeoutError") {
return "Request JS execution limit hit" return "Request JS execution limit hit"
} }
if ("userScriptError" in error) { if (error.code === "USER_SCRIPT_ERROR") {
return error.userScriptError.toString() throw error
} }
return "Error while executing JS" return "Error while executing JS"
} }

View File

@ -16,6 +16,7 @@ import { removeJSRunner, setJSRunner } from "./helpers/javascript"
import manifest from "./manifest.json" import manifest from "./manifest.json"
import { ProcessOptions } from "./types" import { ProcessOptions } from "./types"
import { UserScriptError } from "./errors"
export { helpersToRemoveForJs, getJsHelperList } from "./helpers/list" export { helpersToRemoveForJs, getJsHelperList } from "./helpers/list"
export { FIND_ANY_HBS_REGEX } from "./utilities" export { FIND_ANY_HBS_REGEX } from "./utilities"
@ -230,6 +231,9 @@ export function processStringSync(
return process(string) return process(string)
} }
} catch (err) { } catch (err) {
if (err.code === "USER_SCRIPT_ERROR") {
throw err
}
return input return input
} }
} }
@ -448,7 +452,7 @@ export function convertToJS(hbs: string) {
return `${varBlock}${js}` return `${varBlock}${js}`
} }
export { JsErrorTimeout } from "./errors" export { JsErrorTimeout, UserScriptError } from "./errors"
export function defaultJSSetup() { export function defaultJSSetup() {
if (!isBackendService()) { if (!isBackendService()) {
@ -473,13 +477,17 @@ export function defaultJSSetup() {
try { try {
result.result = ${js}; result.result = ${js};
} catch (e) { } catch (e) {
result.error = e.toString(); result.error = e;
} }
result; result;
` `
return runInNewContext(js, context, { timeout: 1000 }) const result = runInNewContext(js, context, { timeout: 1000 })
if (result.error) {
throw new UserScriptError(result.error)
}
return result.result
}) })
} else { } else {
removeJSRunner() removeJSRunner()