Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: disable pipes on both children and parent properties #1186

Closed
wants to merge 1 commit into from
Closed
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
25 changes: 23 additions & 2 deletions ark/repo/scratch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { type } from "arktype"

const t = type({
foo: "string"
const $Item = type({
type: "string",
"additionalProperties?": "this",
"description?": "string",
"enum?": "string[]",
"example?": "number|string|object|boolean",
"items?": "this",
"required?": "string[]"
})

const DescribedEnum = type({
branches: "string", // type({ unit: "string" }).array().atLeastLength(1),
meta: "string"
}).pipe((v, ctx) => {
console.log(ctx.currentErrorCount, Object.keys(ctx.errors.byPath))
console.log("DescribedEnum is expected to be a valid type, but is: ", v)
return {
// ...
}
}, $Item)
console.clear()
console.log(JSON.stringify(DescribedEnum.json))
const v = DescribedEnum({ domain: "number", meta: "A whole number;example:0" })
console.log(v + "")
19 changes: 18 additions & 1 deletion ark/schema/shared/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import type { Prerequisite, errorContext } from "../kinds.ts"
import type { NodeKind } from "./implement.ts"
import type { StandardSchema } from "./standardSchema.ts"
import type { TraversalContext } from "./traversal.ts"
import { arkKind, pathToPropString, type TraversalPath } from "./utils.ts"
import {
appendPropToPathString,
arkKind,
pathToPropString,
type TraversalPath
} from "./utils.ts"

export type ArkErrorResult = ArkError | ArkErrors

Expand Down Expand Up @@ -87,6 +92,7 @@ export class ArkErrors
}

byPath: Record<string, ArkError> = Object.create(null)
ignorePaths: Record<string, true> = Object.create(null)
count = 0
private mutable: ArkError[] = this as never

Expand Down Expand Up @@ -118,6 +124,17 @@ export class ArkErrors
} else {
this.byPath[error.propString] = error
this.mutable.push(error)

// add superpaths of the error path to ignored
let partialPropString: string = ""
this.ignorePaths[partialPropString] = true
for (let i = 0; i < error.path.length; i++) {
partialPropString = appendPropToPathString(
partialPropString,
error.path[i]
)
this.ignorePaths[partialPropString] = true
}
}
this.count++
}
Expand Down
4 changes: 4 additions & 0 deletions ark/schema/shared/traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,17 @@ export class TraversalContext {
pathHasError(path: TraversalPath): boolean {
if (!this.hasError()) return false

// Check super-paths of the error path
let partialPropString: string = ""
// this.errors.byPath is null prototyped so indexing by string is safe
if (this.errors.byPath[partialPropString]) return true
for (let i = 0; i < path.length; i++) {
partialPropString = appendPropToPathString(partialPropString, path[i])
if (this.errors.byPath[partialPropString]) return true
}
// Check sub-paths of the error path
if (this.errors.ignorePaths[partialPropString]) return true

return false
}

Expand Down
48 changes: 48 additions & 0 deletions ark/type/__tests__/pipe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,54 @@ contextualize(() => {
attest(t({ a: 2 })).snap(3)
})

it("doesn't pipe in child property error", () => {
let calls = 0
const a = type({ a: "number" }).pipe(
() => {
calls++
return { a: "string" }
},
type({ a: "string" })
)
attest(a({ a: 1 })).snap({ a: "string" })
attest(calls).snap(1)
attest(a({ a: "error" }).toString()).snap(
"a must be a number (was a string)"
)
attest(calls).snap(1)
})
it("doesn't pipe in parent property error", () => {
let calls = 0
const a = type({ a: "number" }).pipe(
(v, ctx) => {
if (v.a !== 1) ctx.mustBe("{ a: 1 }")
return { a: "string" }
},
type({
a: type("string").pipe(() => calls++)
})
)
attest(a({ a: 1 })).snap({ a: 0 })
attest(calls).snap(1)
attest(a({ a: 2 }).toString()).snap('must be { a: 1 } (was {"a":2})')
attest(calls).snap(1)
})
it("doesn't pipe child property of parent piped property error", () => {
let calls = 0
const a = type({ a: "number" }).pipe(
() => ({ a: "string" }),
type({
a: type("string").pipe(() => calls++)
})
)
attest(a({ a: 1 })).snap({ a: 0 })
attest(calls).snap(1)
attest(a({ a: "error" }).toString()).snap(
"a must be a number (was a string)"
)
attest(calls).snap(1)
})

it("in array", () => {
const types = scope({
lengthOfString: ["string", "=>", data => data.length],
Expand Down