accounts
Version:
Tempo Accounts SDK
66 lines (61 loc) • 2.5 kB
text/typescript
/**
* A single Zod issue. Modeled loosely so we can accept issues from both
* `zod` (classic, `ZodError`) and `zod/mini` (`$ZodError`).
*/
export type Issue = {
path: readonly PropertyKey[]
code: string
message: string
expected?: string | undefined
values?: readonly unknown[] | undefined
errors?: readonly (readonly Issue[])[] | undefined
}
/**
* Returns true if the given value is a Zod-thrown error. Covers both
* `zod` (classic) and `zod/mini`, which use different `name` values.
*/
export function isError(value: unknown): value is { name: string; issues: readonly Issue[] } {
const name = (value as { name?: string } | null)?.name
return name === 'ZodError' || name === '$ZodError'
}
/**
* Flattens a Zod issue tree into a list of `{ path, message }` entries with
* human-readable messages. Union mismatches are folded by collecting the
* accepted literal values across all branches.
*/
export function flattenIssues(issues: readonly Issue[]): { path: string; message: string }[] {
const result: { path: string; message: string }[] = []
for (const issue of issues) {
if (issue.code === 'invalid_union' && issue.errors?.length) {
// Collect literal values across all union branches: e.g. ['buy', 'sell'].
const values: unknown[] = []
let onlyLiterals = true
for (const branch of issue.errors)
for (const sub of branch) {
if (sub.code === 'invalid_value' && sub.values) values.push(...sub.values)
else onlyLiterals = false
}
if (onlyLiterals && values.length > 0) {
result.push({
path: issue.path.map(String).join('.'),
message: `expected one of: ${values.map((v) => JSON.stringify(v)).join(', ')}`,
})
continue
}
// Fall back to the shortest branch.
const best = issue.errors.reduce((a, b) => (a.length <= b.length ? a : b))
for (const nested of flattenIssues(best))
result.push({
path: [...issue.path.map(String), nested.path].filter(Boolean).join('.'),
message: nested.message,
})
continue
}
let message = issue.message
if (issue.code === 'invalid_type' && issue.expected) message = `expected ${issue.expected}`
else if (issue.code === 'invalid_value' && issue.values?.length)
message = `expected one of: ${issue.values.map((v) => JSON.stringify(v)).join(', ')}`
result.push({ path: issue.path.map(String).join('.'), message })
}
return result
}