Skip to content

Commit

Permalink
feat: node-level expected, actual, problem and message writer config (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ssalbdivad authored Jan 3, 2025
1 parent 12348f0 commit e38d965
Show file tree
Hide file tree
Showing 29 changed files with 208 additions and 71 deletions.
2 changes: 1 addition & 1 deletion ark/attest/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ark/attest",
"version": "0.34.0",
"version": "0.35.0",
"license": "MIT",
"author": {
"name": "David Blass",
Expand Down
2 changes: 1 addition & 1 deletion ark/fast-check/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ark/fast-check",
"version": "0.0.4",
"version": "0.0.5",
"license": "MIT",
"author": {
"name": "David Blass",
Expand Down
2 changes: 1 addition & 1 deletion ark/fs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ark/fs",
"version": "0.30.0",
"version": "0.31.0",
"license": "MIT",
"author": {
"name": "David Blass",
Expand Down
1 change: 1 addition & 0 deletions ark/repo/scratch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { attest } from "@ark/attest"
import { flatMorph } from "@ark/util"
import { ark, type } from "arktype"

Expand Down
26 changes: 25 additions & 1 deletion ark/schema/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { attest, contextualize } from "@ark/attest"
import { $ark, configure, rootSchema, schemaScope } from "@ark/schema"
import {
$ark,
configure,
rootSchema,
schemaScope,
type ArkErrors
} from "@ark/schema"

contextualize(() => {
it("shallow", () => {
Expand Down Expand Up @@ -64,6 +70,24 @@ contextualize(() => {
)
})

it("can configure error writers at a node level", () => {
const customNumber = rootSchema({
meta: {
description: "custom description",
actual: data => `custom actual ${data}`,
problem: ctx => `custom problem ${ctx.expected} ${ctx.actual}`,
message: ctx => `custom message ${ctx.problem}`
},
domain: "number"
})

const out = customNumber("foo") as ArkErrors

attest(out.summary).snap(
"custom message custom problem custom description custom actual foo"
)
})

it("can configure errors by kind at a scope level", () => {
const types = schemaScope(
{ superSpecialString: "string" },
Expand Down
12 changes: 8 additions & 4 deletions ark/schema/__tests__/unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ contextualize(() => {
attest(t.internal.errorContext).equals({
code: "unit",
description: "7n",
unit: 7n
unit: 7n,
meta: {}
})
attest(t.allows(6n)).equals(false)
attest(t.allows(7n)).equals(true)
Expand All @@ -47,7 +48,8 @@ contextualize(() => {
attest(t.internal.errorContext).equals({
code: "unit",
description: "undefined",
unit: undefined
unit: undefined,
meta: {}
})
attest(t.allows(null)).equals(false)
attest(t.allows(undefined)).equals(true)
Expand All @@ -65,7 +67,8 @@ contextualize(() => {
attest(t.internal.errorContext).equals({
code: "unit",
description: "Symbol(status)",
unit: status
unit: status,
meta: {}
})
attest(t.allows(status)).equals(true)
attest(t.allows(Symbol("test"))).equals(false)
Expand All @@ -80,7 +83,8 @@ contextualize(() => {
attest(t.internal.errorContext).equals({
code: "unit",
description: "{}",
unit: o
unit: o,
meta: {}
})
attest(t.allows(o)).equals(true)
attest(t.allows(new Object())).equals(false)
Expand Down
7 changes: 5 additions & 2 deletions ark/schema/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,17 @@ type NodeConfigsByKind = {
export type NodeConfig<kind extends NodeKind = NodeKind> =
NodeConfigsByKind[kind]

type UnknownNodeConfig = {
description?: DescriptionWriter
export interface UnknownErrorWriters {
expected?: ExpectedWriter
actual?: ActualWriter
problem?: ProblemWriter
message?: MessageWriter
}

interface UnknownNodeConfig extends UnknownErrorWriters {
description?: DescriptionWriter
}

export type ResolvedUnknownNodeConfig = requireKeys<
UnknownNodeConfig,
"description"
Expand Down
16 changes: 11 additions & 5 deletions ark/schema/constraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type { NodeCompiler } from "./shared/compile.ts"
import type { BaseNodeDeclaration } from "./shared/declare.ts"
import { Disjoint } from "./shared/disjoint.ts"
import {
compileErrorContext,
compileObjectLiteral,
constraintKeys,
type ConstraintKind,
type IntersectionContext,
Expand Down Expand Up @@ -98,24 +98,30 @@ export abstract class InternalPrimitiveConstraint<
): JsonSchema.Constrainable

traverseApply: TraverseApply<d["prerequisite"]> = (data, ctx) => {
if (!this.traverseAllows(data, ctx)) ctx.error(this.errorContext as never)
if (!this.traverseAllows(data, ctx))
ctx.errorFromNodeContext(this.errorContext as never)
}

compile(js: NodeCompiler): void {
if (js.traversalKind === "Allows") js.return(this.compiledCondition)
else {
js.if(this.compiledNegation, () =>
js.line(`${js.ctx}.error(${this.compiledErrorContext})`)
js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`)
)
}
}

get errorContext(): d["errorContext"] {
return { code: this.kind, description: this.description, ...this.inner }
return {
code: this.kind,
description: this.description,
meta: this.meta,
...this.inner
}
}

get compiledErrorContext(): string {
return compileErrorContext(this.errorContext!)
return compileObjectLiteral(this.errorContext!)
}
}

Expand Down
1 change: 1 addition & 0 deletions ark/schema/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export abstract class BaseNode<
(result, child) => Object.assign(result, child.referencesById),
{ [this.id]: this }
)
readonly compiledMeta: string = JSON.stringify(this.metaJson)

protected cacheGetter<name extends keyof this>(
name: name,
Expand Down
2 changes: 1 addition & 1 deletion ark/schema/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ark/schema",
"version": "0.30.0",
"version": "0.31.0",
"license": "MIT",
"author": {
"name": "David Blass",
Expand Down
2 changes: 1 addition & 1 deletion ark/schema/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export const createNode = (
metaJson = flatMorph(meta, (k, v) => [
k,
k === "examples" ? v : defaultValueSerializer(v)
]) as BaseMeta & dict
]) as never
json.meta = possiblyCollapse(metaJson, "description", true)
}

Expand Down
15 changes: 8 additions & 7 deletions ark/schema/predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
declareNode
} from "./shared/declare.ts"
import {
compileErrorContext,
compileObjectLiteral,
implementNode,
type nodeImplementationOf
} from "./shared/implement.ts"
Expand Down Expand Up @@ -84,18 +84,19 @@ export class PredicateNode extends BaseConstraint<Predicate.Declaration> {
impliedBasis = null

expression: string = this.serializedPredicate
traverseAllows: TraverseAllows = this.predicate
traverseAllows: TraverseAllows = this.predicate as never

errorContext: Predicate.ErrorContext = {
code: "predicate",
description: this.description
description: this.description,
meta: this.meta
}

compiledErrorContext = compileErrorContext(this.errorContext)
compiledErrorContext = compileObjectLiteral(this.errorContext)

traverseApply: TraverseApply = (data, ctx) => {
if (!this.predicate(data, ctx) && !ctx.hasError())
ctx.error(this.errorContext)
if (!this.predicate(data, ctx.external) && !ctx.hasError())
ctx.errorFromNodeContext(this.errorContext)
}

compile(js: NodeCompiler): void {
Expand All @@ -104,7 +105,7 @@ export class PredicateNode extends BaseConstraint<Predicate.Declaration> {
return
}
js.if(`${this.compiledNegation} && !ctx.hasError()`, () =>
js.line(`ctx.error(${this.compiledErrorContext})`)
js.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`)
)
}

Expand Down
16 changes: 11 additions & 5 deletions ark/schema/roots/basis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NodeCompiler } from "../shared/compile.ts"
import { compileErrorContext } from "../shared/implement.ts"
import { compileObjectLiteral } from "../shared/implement.ts"
import type { TraverseApply } from "../shared/traversal.ts"
import { BaseRoot, type InternalRootDeclaration } from "./root.ts"

Expand All @@ -11,22 +11,28 @@ export abstract class InternalBasis<
declare structure: undefined

traverseApply: TraverseApply<d["prerequisite"]> = (data, ctx) => {
if (!this.traverseAllows(data, ctx)) ctx.error(this.errorContext as never)
if (!this.traverseAllows(data, ctx))
ctx.errorFromNodeContext(this.errorContext as never)
}

get errorContext(): d["errorContext"] {
return { code: this.kind, description: this.description, ...this.inner }
return {
code: this.kind,
description: this.description,
meta: this.meta,
...this.inner
}
}

get compiledErrorContext(): string {
return compileErrorContext(this.errorContext!)
return compileObjectLiteral(this.errorContext!)
}

compile(js: NodeCompiler): void {
if (js.traversalKind === "Allows") js.return(this.compiledCondition)
else {
js.if(this.compiledNegation, () =>
js.line(`${js.ctx}.error(${this.compiledErrorContext})`)
js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`)
)
}
}
Expand Down
13 changes: 9 additions & 4 deletions ark/schema/roots/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export class UnionNode extends BaseRoot<Union.Declaration> {
}
errors.push(ctx.popBranch().error!)
}
ctx.error({ code: "union", errors })
ctx.errorFromNodeContext({ code: "union", errors, meta: this.meta })
}

compile(js: NodeCompiler): void {
Expand Down Expand Up @@ -350,10 +350,13 @@ export class UnionNode extends BaseRoot<Union.Declaration> {
`${serializedTypeOfDescriptions}[${condition}]`
: `${serializedPrintable}(${condition})`

js.line(`ctx.error({
// TODO: should have its own error code
js.line(`ctx.errorFromNodeContext({
code: "predicate",
expected: ${serializedExpected},
actual: ${serializedActual},
relativePath: [${serializedPathSegments}]
relativePath: [${serializedPathSegments}],
meta: ${this.compiledMeta}
})`)
}

Expand All @@ -373,7 +376,9 @@ export class UnionNode extends BaseRoot<Union.Declaration> {
)
.line("errors.push(ctx.popBranch().error)")
)
js.line(`ctx.error({ code: "union", errors })`)
js.line(
`ctx.errorFromNodeContext({ code: "union", errors, meta: ${this.compiledMeta} })`
)
} else {
this.branches.forEach(branch =>
js.if(`${js.invoke(branch)}`, () => js.return(true))
Expand Down
4 changes: 3 additions & 1 deletion ark/schema/shared/declare.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { merge, show } from "@ark/util"
import type { UnknownErrorWriters } from "../config.ts"
import type { nodeOfKind, reducibleKindOf } from "../kinds.ts"
import type { Disjoint } from "./disjoint.ts"
import type { NarrowedAttachments, NodeKind } from "./implement.ts"
Expand All @@ -8,7 +9,7 @@ type withMetaPrefixedKeys<o> = {
[k in keyof o as k extends string ? `meta.${k}` : never]: o[k]
}

export interface BaseMeta extends JsonSchema.Meta {
export interface BaseMeta extends JsonSchema.Meta, UnknownErrorWriters {
alias?: string
}

Expand Down Expand Up @@ -44,6 +45,7 @@ interface DeclarationInput {
export interface BaseErrorContext<kind extends NodeKind = NodeKind> {
readonly description?: string
readonly code: kind
readonly meta: BaseMeta
}

export type defaultErrorContext<d extends DeclarationInput> = show<
Expand Down
Loading

0 comments on commit e38d965

Please sign in to comment.