Skip to content
Merged
32 changes: 31 additions & 1 deletion ark/schema/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { attest, contextualize } from "@ark/attest"
import {
$ark,
type ArkErrors,
ArkErrors,
configureSchema,
rootSchema,
schemaScope
Expand Down Expand Up @@ -149,6 +149,20 @@ contextualize(() => {

const errors = nEvenAtLeast2({ n: 1 }) as ArkErrors

it("Array methods allocate plain Array (Symbol.species), not ArkErrors", () => {
const messages = errors.issues.map(e => e.message)
attest(messages instanceof Array).equals(true)
attest(messages.constructor).equals(Array)
attest(messages instanceof ArkErrors).equals(false)

const mapped = errors.map(() => 1)
attest(mapped.constructor).equals(Array)
attest(mapped instanceof ArkErrors).equals(false)

attest(errors.filter(() => true).constructor).equals(Array)
attest(errors.slice().constructor).equals(Array)
})

it("serialization", () => {
attest(errors.toJSON()).snap([
{
Expand Down Expand Up @@ -189,6 +203,22 @@ contextualize(() => {
])
})

it("JSON.stringify round-trip via Standard Schema failure (e.g. HTTP error payload)", () => {
const result = nEvenAtLeast2["~standard"].validate({ n: 1 })
if (!("issues" in result)) throw new Error("expected validation failure")

const messages = result.issues?.map(issue => issue.message)
attest(messages).equals(errors.issues.map(issue => issue.message))
attest(messages instanceof ArkErrors).equals(false)

const parsed = JSON.parse(
JSON.stringify({ issues: result.issues, messages })
) as { issues: unknown[]; messages: string[] }

attest(parsed.issues).equals(JSON.parse(JSON.stringify(errors)))
attest(parsed.messages).equals(messages!)
})
Comment thread
gabroberge marked this conversation as resolved.

it("flatByPath", () => {
attest(errors.flatByPath).snap({
n: [
Expand Down
9 changes: 9 additions & 0 deletions ark/schema/shared/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ export class ArkErrors
{
readonly [arkKind] = "errors"

/**
* Inherited array methods (`map`, `filter`, `slice`, …) return a plain
* `Array`, not another `ArkErrors`, so callbacks that return primitives
* (e.g. `issues.map(i => i.message)`) cannot populate a new `ArkErrors` instance.
*/
static get [Symbol.species](): ArrayConstructor {
return Array
}

protected ctx: Traversal

constructor(ctx: Traversal) {
Expand Down
Loading