more robust isValue() check

This commit is contained in:
Chris Wanstrath 2025-11-05 15:43:30 -08:00
parent bd1736b474
commit bffb83a528
2 changed files with 25 additions and 2 deletions

View File

@ -6,6 +6,7 @@ import { VM } from "./vm"
export type NativeFunction = (...args: Value[]) => Promise<Value> | Value export type NativeFunction = (...args: Value[]) => Promise<Value> | Value
export type TypeScriptFunction = (this: VM, ...args: any[]) => any export type TypeScriptFunction = (this: VM, ...args: any[]) => any
const REEF_FUNCTION = Symbol('__reefFunction') const REEF_FUNCTION = Symbol('__reefFunction')
const VALUE_TYPES = new Set(['null', 'boolean', 'number', 'string', 'array', 'dict', 'regex', 'native', 'function'])
export type Value = export type Value =
| { type: 'null', value: null } | { type: 'null', value: null }
@ -39,7 +40,7 @@ export type FunctionDef = {
} }
export function isValue(v: any): boolean { export function isValue(v: any): boolean {
return !!(v && typeof v === 'object' && v.type && 'value' in v) return !!(v && typeof v === 'object' && VALUE_TYPES.has(v.type) && 'value' in v)
} }
export function toValue(v: any, vm?: VM): Value /* throws */ { export function toValue(v: any, vm?: VM): Value /* throws */ {

View File

@ -71,7 +71,6 @@ test("isValue - used by toValue to detect already-converted values", () => {
}) })
test("isValue - edge cases with type and value properties", () => { test("isValue - edge cases with type and value properties", () => {
// extra properties
expect(isValue({ type: 'number', value: 42, extra: 'data' })).toBe(true) expect(isValue({ type: 'number', value: 42, extra: 'data' })).toBe(true)
expect(isValue({ type: null, value: 42 })).toBe(false) expect(isValue({ type: null, value: 42 })).toBe(false)
@ -81,6 +80,29 @@ test("isValue - edge cases with type and value properties", () => {
expect(isValue({ typ: 'number', value: 42 })).toBe(false) expect(isValue({ typ: 'number', value: 42 })).toBe(false)
}) })
test("isValue - rejects objects with invalid type values", () => {
expect(isValue({ type: 'text', value: 'Bob' })).toBe(false)
expect(isValue({ type: 'email', value: 'test@example.com' })).toBe(false)
expect(isValue({ type: 'password', value: 'secret' })).toBe(false)
expect(isValue({ type: 'checkbox', value: true })).toBe(false)
expect(isValue({ type: 'custom', value: 123 })).toBe(false)
expect(isValue({ type: 'unknown', value: null })).toBe(false)
})
test("toValue - correctly handles HTML input props", async () => {
const { VM, toBytecode, toValue } = await import("#reef")
const bytecode = toBytecode([["HALT"]])
const vm = new VM(bytecode)
const inputProps = { type: 'text', value: 'Bob' }
const converted = toValue(inputProps, vm)
expect(converted.type).toBe('dict')
expect(converted.value.get('type')).toEqual({ type: 'string', value: 'text' })
expect(converted.value.get('value')).toEqual({ type: 'string', value: 'Bob' })
})
test("toValue - converts wrapped Reef functions back to original Value", async () => { test("toValue - converts wrapped Reef functions back to original Value", async () => {
const { VM, toBytecode, fnFromValue } = await import("#reef") const { VM, toBytecode, fnFromValue } = await import("#reef")