Skip to content

Commit

Permalink
fix an incorrect return on certain pipes with validated output (#974)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssalbdivad committed May 25, 2024
1 parent cd6cff2 commit 52be860
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["arktype", "@arktype/repo", "@arktype/docs", "@arktype/arkdark"],
"ignore": ["arktype", "@arktype/repo", "@arktype/docs", "arkdark"],
"bumpVersionsWithWorkspaceProtocolOnly": true
}
5 changes: 5 additions & 0 deletions .changeset/poor-buckets-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@arktype/schema": patch
---

Pipe and narrow bug fixes (see [arktype CHANGELOG](../type/CHANGELOG.md))
2 changes: 1 addition & 1 deletion ark/attest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"bunTest": "bun test --preload ../repo/bunTestSetup.ts"
},
"dependencies": {
"arktype": "2.0.0-dev.13",
"arktype": "latest",
"@arktype/fs": "workspace:*",
"@arktype/util": "workspace:*",
"@typescript/vfs": "1.5.0",
Expand Down
10 changes: 5 additions & 5 deletions ark/dark/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@arktype/arkdark",
"name": "arkdark",
"private": true,
"displayName": "ArkDark",
"description": "ArkType syntax highlighting and theme⛵",
"version": "5.1.3",
"version": "5.1.4",
"publisher": "arktypeio",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -52,15 +52,15 @@
},
"errorLens.replace": [
{
"matcher": "^(?:Type|Argument of type) '.*' is not assignable to type 'keyError<\"(.*)\">'\\.$",
"matcher": "^(?:Type|Argument of type) '.*' is not assignable to (?:parameter of )?type 'keyError<\"(.*)\">'\\.$",
"message": "$1"
},
{
"matcher": "^(?:Type|Argument of type) '.*' is not assignable to type '\"(.*\\u200A)\"'\\.$",
"matcher": "^(?:Type|Argument of type) '.*' is not assignable to (?:parameter of )?type '\"(.*\\u200A)\"'\\.$",
"message": "$1"
},
{
"matcher": "^(?:Type|Argument of type) '\"(.*)\"' is not assignable to type '(\"\\1.*\")'\\.$",
"matcher": "^(?:Type|Argument of type) '\"(.*)\"' is not assignable to (?:parameter of )?type '(\"\\1.*\")'\\.$",
"message": "$2"
},
{
Expand Down
14 changes: 13 additions & 1 deletion ark/docs/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,23 @@ import { HomeDemo } from "../../components/HomeDemo.tsx"
Zod](https://moltar.github.io/typescript-runtime-type-benchmarks/) with
editor performance that will remind you how autocomplete is supposed to feel
</Card>
<Card title="Clear and concise" icon="magnifier">
<Card title="Clear and concise" icon="comment-alt">
Schemas are half as long and twice as readable with hovers that tell you
just what really matters
</Card>
<Card title="Better errors" icon="error">
Deeply customizable and composable messages with great defaults
</Card>
<Card title="Deeply introspectable" icon="seti:code-search">
ArkType uses set theory to understand and expose the relationships between
your types at runtime the way TypeScript does at compile time
</Card>
<Card title="Smarter by default" icon="seti:smarty">
Every schema you define is internally optimized to provide the clearest,
fastest validation possible
</Card>
{/* <Card title="Portable" icon="seti:json">
Most definitions are just objects and strings- take them across the stack or
even outside JS altogether
</Card> */}
</CardGrid>
36 changes: 10 additions & 26 deletions ark/repo/scratch.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
import { type } from "arktype"

const parseBigint = type("string", "=>", (s, ctx) => {
try {
return BigInt(s)
} catch {
return ctx.error("a valid number")
}
const user = type({
name: "string",
age: "number"
})

// or
const parseUser = type("string").pipe(s => JSON.parse(s), user)

const parseBigint2 = type("string").pipe((s, ctx) => {
try {
return BigInt(s)
} catch {
return ctx.error("a valid number")
}
})

const Test = type({
group: {
nested: {
value: parseBigint
}
}
})
const validUser = parseUser(`{ "name": "David", "age": 30 }`) //?
// ^?

const myFunc = () => {
const out = Test({})
if (out instanceof type.errors) return
const invalidUser = parseUser(`{ "name": "David" }`)
// ^?

const value: bigint = out.group.nested.value
if (invalidUser instanceof type.errors) {
console.log(invalidUser.summary)
}
5 changes: 4 additions & 1 deletion ark/schema/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ export abstract class BaseNode<
)
return data

if (pipedFromCtx) return this.traverseApply(data, pipedFromCtx)
if (pipedFromCtx) {
this.traverseApply(data, pipedFromCtx)
return pipedFromCtx.data
}

const ctx = new TraversalContext(data, this.$.resolvedConfig)
this.traverseApply(data, ctx)
Expand Down
61 changes: 31 additions & 30 deletions ark/schema/shared/traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,41 +49,42 @@ export class TraversalContext {
finalize(): unknown {
if (this.hasError()) return this.errors

if (this.queuedMorphs.length) {
for (let i = 0; i < this.queuedMorphs.length; i++) {
const { path, morphs } = this.queuedMorphs[i]
// invoking morphs that are Nodes will reuse this context, potentially
// adding additional morphs, so we have to continue looping until
// queuedMorphs is empty rather than iterating over the list once
while (this.queuedMorphs.length) {
const { path, morphs } = this.queuedMorphs.shift()!

const key = path.at(-1)
const key = path.at(-1)

let parent: any
let parent: any

if (key !== undefined) {
// find the object on which the key to be morphed exists
parent = this.root
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++)
parent = parent[path[pathIndex]]
}
if (key !== undefined) {
// find the object on which the key to be morphed exists
parent = this.root
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++)
parent = parent[path[pathIndex]]
}

this.path = path
for (const morph of morphs) {
const result = morph(
parent === undefined ? this.root : parent[key!],
this
)
if (result instanceof ArkErrors) return result
if (this.hasError()) return this.errors
if (result instanceof ArkError) {
// if an ArkError was returned but wasn't added to these
// errors, add it then return
this.error(result)
return this.errors
}

// apply the morph function and assign the result to the
// corresponding property, or to root if path is empty
if (parent === undefined) this.root = result
else parent[key!] = result
this.path = path
for (const morph of morphs) {
const result = morph(
parent === undefined ? this.root : parent[key!],
this
)
if (result instanceof ArkErrors) return result
if (this.hasError()) return this.errors
if (result instanceof ArkError) {
// if an ArkError was returned but wasn't added to these
// errors, add it then return
this.error(result)
return this.errors
}

// apply the morph function and assign the result to the
// corresponding property, or to root if path is empty
if (parent === undefined) this.root = result
else parent[key!] = result
}
}
return this.root
Expand Down
41 changes: 41 additions & 0 deletions ark/type/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
# arktype

## 2.0.0-dev.16

- Fix an incorrect return value on pipe sequences like the following:

```ts
const Amount = type(
"string",
":",
(s, ctx) => Number.isInteger(Number(s)) || ctx.invalid("number")
)
.pipe((s, ctx) => {
try {
return BigInt(s)
} catch {
return ctx.error("a non-decimal number")
}
})
.narrow((amount, ctx) => true)

const Token = type("7<string<=120")
.pipe(s => s.toLowerCase())
.narrow((s, ctx) => true)

const $ = scope({
Asset: {
token: Token,
amount: Amount
},
Assets: () => $.type("Asset[]>=1").pipe(assets => assets)
})

const types = $.export()

// now correctly returns { token: "lovelace", amount: 5000000n }
const out = types.Assets([{ token: "lovelace", amount: "5000000" }])

// (was previously { token: undefined, amount: undefined })
```

https://github.com/arktypeio/arktype/pull/974

## 2.0.0-dev.15

- Fix a crash when piping to nested paths (see https://github.com/arktypeio/arktype/issues/968)
Expand Down
6 changes: 6 additions & 0 deletions ark/type/__tests__/pipe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ contextualize(() => {
attest(t.json).equals(expected.json)
})

it("disjoint", () => {
attest(() => type("number>5").pipe(type("number<3"))).throws.snap(
"ParseError: Intersection of <3 and >5 results in an unsatisfiable type"
)
})

it("uses pipe for many consecutive types", () => {
const t = type({ a: "1" }).pipe(
type({ b: "1" }),
Expand Down
35 changes: 35 additions & 0 deletions ark/type/__tests__/realWorld.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,39 @@ nospace must be matched by ^\\S*$ (was "One space")`)

attest(out).snap({ assets: { a: "1n" } })
})

// https://discord.com/channels/957797212103016458/957804102685982740/1243850690644934677
it("more chained pipes/narrows", () => {
const Amount = type(
"string",
":",
(s, ctx) => Number.isInteger(Number(s)) || ctx.invalid("number")
)
.pipe((s, ctx) => {
try {
return BigInt(s)
} catch {
return ctx.error("a non-decimal number")
}
})
.narrow((amount, ctx) => true)

const Token = type("7<string<=120")
.pipe(s => s.toLowerCase())
.narrow((s, ctx) => true)

const $ = scope({
Asset: {
token: Token,
amount: Amount
},
Assets: () => $.type("Asset[]>=1").pipe(assets => assets)
})

const types = $.export()

const out = types.Assets([{ token: "lovelace", amount: "5000000" }])

attest(out).snap([{ token: "lovelace", amount: "5000000n" }])
})
})
2 changes: 1 addition & 1 deletion ark/type/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "arktype",
"description": "TypeScript's 1:1 validator, optimized from editor to runtime",
"version": "2.0.0-dev.15",
"version": "2.0.0-dev.16",
"license": "MIT",
"author": {
"name": "David Blass",
Expand Down

0 comments on commit 52be860

Please sign in to comment.