Skip to content

Commit 0736264

Browse files
authored
fix(inspect): show function argument parsing errors (#455)
1 parent bdcf501 commit 0736264

3 files changed

Lines changed: 104 additions & 4 deletions

File tree

‎plugins/inspect/app/components/FunctionRow.vue‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<script setup lang="ts">
22
import type { RpcFunctionInfo } from '../connect'
33
import ActionButton from '@antfu/design/components/Action/ActionButton.vue'
4-
import { computed, ref } from 'vue'
4+
import FeedbackTip from '@antfu/design/components/Feedback/FeedbackTip.vue'
5+
import { computed, ref, useId } from 'vue'
56
import FunctionName from './FunctionName.vue'
67
import JsonView from './JsonView.vue'
78
@@ -11,6 +12,7 @@ const props = defineProps<{
1112
results: Record<string, any>
1213
pending: Record<string, boolean>
1314
isStatic: boolean
15+
invokeError?: string
1416
}>()
1517
1618
const emit = defineEmits<{
@@ -19,6 +21,7 @@ const emit = defineEmits<{
1921
}>()
2022
2123
const expanded = ref(false)
24+
const invokeErrorId = useId()
2225
2326
const clickable = computed(() =>
2427
props.fn.invokable || !!props.fn.agent || props.fn.hasArgs || props.fn.hasReturns,
@@ -67,7 +70,25 @@ function toggle(): void {
6770
<div class="label">
6871
Invoke with positional args as a JSON array
6972
</div>
70-
<textarea :value="argsInput[fn.name]" class="args" spellcheck="false" placeholder="[]" @input="emit('updateArgs', fn.name, ($event.target as HTMLTextAreaElement).value)" />
73+
<textarea
74+
:value="argsInput[fn.name]"
75+
class="args"
76+
spellcheck="false"
77+
placeholder="[]"
78+
:aria-invalid="!!invokeError"
79+
:aria-describedby="invokeError ? invokeErrorId : undefined"
80+
@input="emit('updateArgs', fn.name, ($event.target as HTMLTextAreaElement).value)"
81+
/>
82+
<FeedbackTip
83+
v-if="invokeError"
84+
:id="invokeErrorId"
85+
class="mt-2"
86+
type="error"
87+
icon="i-ph-warning-circle-duotone"
88+
role="alert"
89+
>
90+
{{ invokeError }}
91+
</FeedbackTip>
7192
<div style="margin-top: 8px; display: flex; gap: 8px; align-items: center;">
7293
<ActionButton
7394
variant="primary"

‎plugins/inspect/app/components/FunctionsView.stories.ts‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { Meta, StoryObj } from '@storybook/vue3-vite'
2+
import type { RpcFunctionInfo } from '../connect'
3+
import { expect, fn, userEvent, within } from 'storybook/test'
24
import FunctionsView from './FunctionsView.vue'
35

46
const meta = {
@@ -119,3 +121,71 @@ export const WithResultsAndPending: Story = {
119121
},
120122
},
121123
}
124+
125+
const query: RpcFunctionInfo = {
126+
name: 'audit:echo',
127+
type: 'query',
128+
jsonSerializable: true,
129+
snapshot: false,
130+
cacheable: false,
131+
hasArgs: true,
132+
hasReturns: false,
133+
hasDump: false,
134+
hasSetup: true,
135+
hasHandler: true,
136+
invokable: true,
137+
}
138+
139+
export const InvalidArguments: Story = {
140+
args: {
141+
...Default.args,
142+
functions: [query],
143+
onInvoke: fn(),
144+
},
145+
play: async ({ args, canvasElement }) => {
146+
const canvas = within(canvasElement)
147+
await userEvent.click(canvas.getByText('echo'))
148+
const input = canvas.getByPlaceholderText('[]')
149+
const invoke = canvas.getByRole('button', { name: 'Invoke' })
150+
151+
await userEvent.type(input, '{{')
152+
await userEvent.click(invoke)
153+
const error = canvas.getByRole('alert')
154+
await expect(error).toHaveTextContent('Invalid JSON arguments:')
155+
await expect(input).toHaveAttribute('aria-invalid', 'true')
156+
await expect(input).toHaveAttribute('aria-describedby', error.id)
157+
await expect(args.onInvoke).not.toHaveBeenCalled()
158+
159+
await userEvent.clear(input)
160+
await expect(canvas.queryByRole('alert')).not.toBeInTheDocument()
161+
await expect(input).toHaveAttribute('aria-invalid', 'false')
162+
await userEvent.type(input, '[["recovered"]')
163+
await userEvent.click(invoke)
164+
await expect(args.onInvoke).toHaveBeenLastCalledWith(query, ['recovered'])
165+
166+
await userEvent.clear(input)
167+
await userEvent.type(input, '42')
168+
await userEvent.click(invoke)
169+
await expect(args.onInvoke).toHaveBeenLastCalledWith(query, [42])
170+
},
171+
}
172+
173+
export const InvalidArgumentsAfterSuccess: Story = {
174+
args: {
175+
...InvalidArguments.args,
176+
results: {
177+
'audit:echo': { ok: true, result: 'previous result', durationMs: 1 },
178+
},
179+
onInvoke: fn(),
180+
},
181+
play: async ({ args, canvasElement }) => {
182+
const canvas = within(canvasElement)
183+
await userEvent.click(canvas.getByText('echo'))
184+
const input = canvas.getByPlaceholderText('[]')
185+
await userEvent.type(input, '{{')
186+
await userEvent.click(canvas.getByRole('button', { name: 'Invoke' }))
187+
await expect(canvas.getByRole('alert')).toHaveTextContent('Invalid JSON arguments:')
188+
await expect(canvas.getByText('"previous result"')).toBeVisible()
189+
await expect(args.onInvoke).not.toHaveBeenCalled()
190+
},
191+
}

‎plugins/inspect/app/components/FunctionsView.vue‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const emit = defineEmits<{
1616
1717
const search = ref('')
1818
const argsInput = reactive<Record<string, string>>({})
19+
const invokeErrors = reactive<Record<string, string>>({})
1920
2021
const filtered = computed(() => {
2122
const list = props.functions ?? []
@@ -38,10 +39,17 @@ function invoke(fn: RpcFunctionInfo): void {
3839
parsed = Array.isArray(raw) ? raw : [raw]
3940
}
4041
catch (e) {
41-
throw new Error(`Invalid JSON args: ${(e as Error).message}`)
42+
invokeErrors[fn.name] = `Invalid JSON arguments: ${(e as Error).message}`
43+
return
4244
}
45+
delete invokeErrors[fn.name]
4346
emit('invoke', fn, parsed)
4447
}
48+
49+
function updateArgs(name: string, value: string): void {
50+
argsInput[name] = value
51+
delete invokeErrors[name]
52+
}
4553
</script>
4654

4755
<template>
@@ -70,11 +78,12 @@ function invoke(fn: RpcFunctionInfo): void {
7078
:key="fn.name"
7179
:fn="fn"
7280
:args-input="argsInput"
81+
:invoke-error="invokeErrors[fn.name]"
7382
:results="results"
7483
:pending="pending"
7584
:is-static="isStatic"
7685
@invoke="invoke"
77-
@update-args="(n, v) => argsInput[n] = v"
86+
@update-args="updateArgs"
7887
/>
7988
</div>
8089
</div>

0 commit comments

Comments
 (0)