diff --git a/ark/schema/__tests__/errors.test.ts b/ark/schema/__tests__/errors.test.ts index 27568fc647..c34a29c44a 100644 --- a/ark/schema/__tests__/errors.test.ts +++ b/ark/schema/__tests__/errors.test.ts @@ -1,7 +1,7 @@ import { attest, contextualize } from "@ark/attest" import { $ark, - type ArkErrors, + ArkErrors, configureSchema, rootSchema, schemaScope @@ -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([ { @@ -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!) + }) + it("flatByPath", () => { attest(errors.flatByPath).snap({ n: [ diff --git a/ark/schema/shared/errors.ts b/ark/schema/shared/errors.ts index 08a774e836..c397578eee 100644 --- a/ark/schema/shared/errors.ts +++ b/ark/schema/shared/errors.ts @@ -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) {