Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions ark/schema/structure/sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,13 +419,18 @@ export class SequenceNode extends BaseConstraint<Sequence.Declaration> {
}

traverseApply: TraverseApply<array> = (data, ctx) => {
const errorCount = ctx.currentErrorCount
let i = 0
for (; i < data.length; i++) {
traverseKey(
i,
() => this.elementAtIndex(data, i).node.traverseApply(data[i], ctx),
ctx
)
// bail out of subsequent elements in fail-fast mode (e.g. inside a
// union branch) so we don't traverse an element whose basis has
// already failed - see https://github.com/arktypeio/arktype/issues/1458
if (ctx.failFast && ctx.currentErrorCount > errorCount) return
}
}

Expand All @@ -435,9 +440,17 @@ export class SequenceNode extends BaseConstraint<Sequence.Declaration> {

// minLength/maxLength compilation should be handled by Intersection
compile(js: NodeCompiler): void {
// like Structure, bail out of subsequent elements in fail-fast mode
// (e.g. inside a union branch) so we don't traverse an element whose
// basis has already failed - see
// https://github.com/arktypeio/arktype/issues/1458
if (js.traversalKind === "Apply") js.initializeErrorCount()

if (this.prefix) {
for (const [i, node] of this.prefix.entries())
for (const [i, node] of this.prefix.entries()) {
js.traverseKey(`${i}`, `data[${i}]`, node)
if (js.traversalKind === "Apply") js.returnIfFailFast()
}
}

for (const [i, node] of this.defaultablesAndOptionals.entries()) {
Expand All @@ -446,6 +459,7 @@ export class SequenceNode extends BaseConstraint<Sequence.Declaration> {
js.traversalKind === "Allows" ? js.return(true) : js.return()
)
js.traverseKey(dataIndex, `data[${dataIndex}]`, node)
if (js.traversalKind === "Apply") js.returnIfFailFast()
}

if (this.variadic) {
Expand All @@ -457,13 +471,17 @@ export class SequenceNode extends BaseConstraint<Sequence.Declaration> {
}
js.for(
`i < ${this.postfix ? "firstPostfixIndex" : "data.length"}`,
() => js.traverseKey("i", "data[i]", this.variadic!),
() => {
js.traverseKey("i", "data[i]", this.variadic!)
return js.traversalKind === "Apply" ? js.returnIfFailFast() : js
},
this.prevariadic.length
)
if (this.postfix) {
for (const [i, node] of this.postfix.entries()) {
const keyExpression = `firstPostfixIndex + ${i}`
js.traverseKey(keyExpression, `data[${keyExpression}]`, node)
if (js.traversalKind === "Apply") js.returnIfFailFast()
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions ark/type/__tests__/union.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ contextualize(() => {
attest(T.json).equals(type("string|bigint|number|boolean").json)
})

it("union of object arrays doesn't throw on non-object element", () => {
// https://github.com/arktypeio/arktype/issues/1458
// checking a later element whose basis (object) fails must not attempt
// an `in` check against a primitive earlier collected element
const TypeAB = type.or("string", { objB: "string" })
const TypeC = type({ objC: "string" })
const TypeABC = type([TypeAB.array(), "|", TypeC.array()])

attest(
TypeABC([{ objB: "text b" }, "test a", { objC: "text c" }]).toString()
).snap(
"value at [2].objB must be a string (was missing) or [0].objC must be a string (was missing)"
)

attest(
TypeABC(["test a", { objB: "text b" }, { objC: "text c" }]).toString()
).snap(
"value at [2].objB must be a string (was missing) or [0] must be an object (was a string)"
)
})

it("length stress", () => {
// as of TS 5.1, can handle a max of 46 branches before an inifinitely
// deep error not the end of the world if this changes slightly, but
Expand Down
Loading