Skip to content

Commit 28f38fc

Browse files
authored
Remove Zod from named errors (#26982)
1 parent 8feb4a3 commit 28f38fc

20 files changed

Lines changed: 197 additions & 245 deletions

File tree

packages/core/src/util/error.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,51 @@
1-
import z from "zod"
1+
import { Schema } from "effect"
22

33
export abstract class NamedError extends Error {
4-
abstract schema(): z.core.$ZodType
5-
abstract toObject(): { name: string; data: any }
4+
abstract schema(): Schema.Top
5+
abstract toObject(): { name: string; data: unknown }
66

77
static hasName(error: unknown, name: string): boolean {
88
return (
99
typeof error === "object" && error !== null && "name" in error && (error as Record<string, unknown>).name === name
1010
)
1111
}
1212

13-
static create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data) {
14-
const schema = z
15-
.object({
16-
name: z.literal(name),
17-
data,
18-
})
19-
.meta({
20-
ref: name,
21-
})
13+
static create<Name extends string, Fields extends Schema.Struct.Fields>(
14+
name: Name,
15+
fields: Fields,
16+
): ReturnType<typeof NamedError.createSchemaClass<Name, Schema.Struct<Fields>>>
17+
static create<Name extends string, DataSchema extends Schema.Top>(
18+
name: Name,
19+
data: DataSchema,
20+
): ReturnType<typeof NamedError.createSchemaClass<Name, DataSchema>>
21+
static create<Name extends string>(name: Name, data: Schema.Top | Schema.Struct.Fields) {
22+
return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data))
23+
}
24+
25+
private static createSchemaClass<Name extends string, DataSchema extends Schema.Top>(name: Name, data: DataSchema) {
26+
const schema = Schema.Struct({
27+
name: Schema.Literal(name),
28+
data,
29+
}).annotate({ identifier: name })
30+
type Data = Schema.Schema.Type<DataSchema>
31+
2232
const result = class extends NamedError {
2333
public static readonly Schema = schema
34+
public static readonly EffectSchema = schema
35+
public static readonly tag = name
2436

25-
public override readonly name = name as Name
37+
public override readonly name = name
2638

2739
constructor(
28-
public readonly data: z.input<Data>,
40+
public readonly data: Data,
2941
options?: ErrorOptions,
3042
) {
3143
super(name, options)
3244
this.name = name
3345
}
3446

35-
static isInstance(input: any): input is InstanceType<typeof result> {
36-
return typeof input === "object" && "name" in input && input.name === name
47+
static isInstance(input: unknown): input is InstanceType<typeof result> {
48+
return NamedError.hasName(input, name)
3749
}
3850

3951
schema() {
@@ -51,10 +63,7 @@ export abstract class NamedError extends Error {
5163
return result
5264
}
5365

54-
public static readonly Unknown = NamedError.create(
55-
"UnknownError",
56-
z.object({
57-
message: z.string(),
58-
}),
59-
)
66+
public static readonly Unknown = NamedError.create("UnknownError", {
67+
message: Schema.String,
68+
})
6069
}

packages/enterprise/src/routes/share/[shareID].tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { Binary } from "@opencode-ai/core/util/binary"
1515
import { NamedError } from "@opencode-ai/core/util/error"
1616
import { DateTime } from "luxon"
1717
import { createStore } from "solid-js/store"
18-
import z from "zod"
1918
import NotFound from "../[...404]"
2019
import { Tabs } from "@opencode-ai/ui/tabs"
2120
import { MessageNav } from "@opencode-ai/ui/message-nav"
@@ -33,13 +32,28 @@ const ClientOnlyWorkerPoolProvider = clientOnly(() =>
3332
})),
3433
)
3534

36-
const SessionDataMissingError = NamedError.create(
37-
"SessionDataMissingError",
38-
z.object({
39-
sessionID: z.string(),
40-
message: z.string().optional(),
41-
}),
42-
)
35+
class SessionDataMissingError extends NamedError {
36+
public override readonly name = "SessionDataMissingError"
37+
38+
constructor(
39+
public readonly data: { sessionID: string; message?: string },
40+
options?: ErrorOptions,
41+
) {
42+
super("SessionDataMissingError", options)
43+
}
44+
45+
static isInstance(input: unknown): input is SessionDataMissingError {
46+
return NamedError.hasName(input, "SessionDataMissingError")
47+
}
48+
49+
schema(): never {
50+
throw new Error("SessionDataMissingError does not expose a schema")
51+
}
52+
53+
toObject() {
54+
return { name: this.name, data: this.data }
55+
}
56+
}
4357

4458
const getData = query(async (shareID) => {
4559
"use server"

packages/opencode/src/cli/ui.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import z from "zod"
21
import { EOL } from "os"
32
import { NamedError } from "@opencode-ai/core/util/error"
3+
import { Schema } from "effect"
44
import { logo as glyphs } from "./logo"
55

66
const wordmark = [
@@ -10,7 +10,7 @@ const wordmark = [
1010
`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`,
1111
]
1212

13-
export const CancelledError = NamedError.create("UICancelledError", z.void())
13+
export const CancelledError = NamedError.create("UICancelledError", Schema.optional(Schema.Void))
1414

1515
export const Style = {
1616
TEXT_HIGHLIGHT: "\x1b[96m",

packages/opencode/src/config/config.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as Log from "@opencode-ai/core/util/log"
22
import path from "path"
33
import { pathToFileURL } from "url"
44
import os from "os"
5-
import z from "zod"
65
import { mergeDeep } from "remeda"
76
import { Global } from "@opencode-ai/core/global"
87
import fsNode from "fs/promises"
@@ -357,14 +356,11 @@ function writableGlobal(info: Info) {
357356
return next
358357
}
359358

360-
export const ConfigDirectoryTypoError = NamedError.create(
361-
"ConfigDirectoryTypoError",
362-
z.object({
363-
path: z.string(),
364-
dir: z.string(),
365-
suggestion: z.string(),
366-
}),
367-
)
359+
export const ConfigDirectoryTypoError = NamedError.create("ConfigDirectoryTypoError", {
360+
path: Schema.String,
361+
dir: Schema.String,
362+
suggestion: Schema.String,
363+
})
368364

369365
export const layer = Layer.effect(
370366
Service,
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
export * as ConfigError from "./error"
22

3-
import z from "zod"
43
import { NamedError } from "@opencode-ai/core/util/error"
4+
import { Schema } from "effect"
55

6-
export const JsonError = NamedError.create(
7-
"ConfigJsonError",
8-
z.object({
9-
path: z.string(),
10-
message: z.string().optional(),
6+
const Issue = Schema.StructWithRest(
7+
Schema.Struct({
8+
message: Schema.String,
9+
path: Schema.Array(Schema.String),
1110
}),
11+
[Schema.Record(Schema.String, Schema.Unknown)],
1212
)
1313

14-
export const InvalidError = NamedError.create(
15-
"ConfigInvalidError",
16-
z.object({
17-
path: z.string(),
18-
issues: z.custom<z.core.$ZodIssue[]>().optional(),
19-
message: z.string().optional(),
20-
}),
21-
)
14+
export const JsonError = NamedError.create("ConfigJsonError", {
15+
path: Schema.String,
16+
message: Schema.optional(Schema.String),
17+
})
18+
19+
export const InvalidError = NamedError.create("ConfigInvalidError", {
20+
path: Schema.String,
21+
issues: Schema.optional(Schema.Array(Issue)),
22+
message: Schema.optional(Schema.String),
23+
})

packages/opencode/src/config/markdown.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NamedError } from "@opencode-ai/core/util/error"
22
import matter from "gray-matter"
3-
import { z } from "zod"
3+
import { Schema } from "effect"
44
import { Filesystem } from "@/util/filesystem"
55

66
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
@@ -88,12 +88,9 @@ export async function parse(filePath: string) {
8888
}
8989
}
9090

91-
export const FrontmatterError = NamedError.create(
92-
"ConfigFrontmatterError",
93-
z.object({
94-
path: z.string(),
95-
message: z.string(),
96-
}),
97-
)
91+
export const FrontmatterError = NamedError.create("ConfigFrontmatterError", {
92+
path: Schema.String,
93+
message: Schema.String,
94+
})
9895

9996
export * as ConfigMarkdown from "./markdown"

packages/opencode/src/config/parse.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ export * as ConfigParse from "./parse"
22

33
import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser"
44
import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect"
5-
import type z from "zod"
65
import type { DeepMutable } from "@opencode-ai/core/schema"
76
import { InvalidError, JsonError } from "./error"
87

@@ -48,7 +47,7 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
4847
keys: extra,
4948
path: [],
5049
message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`,
51-
} as z.core.$ZodIssue,
50+
},
5251
],
5352
})
5453
}
@@ -61,8 +60,12 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
6160
{
6261
path: source,
6362
issues: EffectSchema.isSchemaError(error)
64-
? (SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues as z.core.$ZodIssue[])
65-
: ([{ code: "custom", message: String(error), path: [] }] as z.core.$ZodIssue[]),
63+
? SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues.map((issue) => ({
64+
...issue,
65+
message: issue.message,
66+
path: issue.path?.map(String) ?? [],
67+
}))
68+
: [{ message: String(error), path: [] }],
6669
},
6770
{ cause: error },
6871
)

packages/opencode/src/ide/index.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { BusEvent } from "@/bus/bus-event"
2-
import z from "zod"
32
import { Schema } from "effect"
43
import { NamedError } from "@opencode-ai/core/util/error"
54
import * as Log from "@opencode-ai/core/util/log"
@@ -24,14 +23,11 @@ export const Event = {
2423
),
2524
}
2625

27-
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({}))
26+
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})
2827

29-
export const InstallFailedError = NamedError.create(
30-
"InstallFailedError",
31-
z.object({
32-
stderr: z.string(),
33-
}),
34-
)
28+
export const InstallFailedError = NamedError.create("InstallFailedError", {
29+
stderr: Schema.String,
30+
})
3531

3632
export function ide() {
3733
if (process.env["TERM_PROGRAM"] === "vscode") {

packages/opencode/src/index.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { PluginCommand } from "./cli/cmd/plug"
3939
import { Heap } from "./cli/heap"
4040
import { drizzle } from "drizzle-orm/bun-sqlite"
4141
import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
42+
import { isRecord } from "@/util/record"
4243

4344
const processMetadata = ensureProcessMetadata("main")
4445

@@ -203,13 +204,6 @@ try {
203204
}
204205
} catch (e) {
205206
let data: Record<string, any> = {}
206-
if (e instanceof NamedError) {
207-
const obj = e.toObject()
208-
Object.assign(data, {
209-
...obj.data,
210-
})
211-
}
212-
213207
if (e instanceof Error) {
214208
Object.assign(data, {
215209
name: e.name,
@@ -219,6 +213,16 @@ try {
219213
})
220214
}
221215

216+
if (e instanceof NamedError) {
217+
const obj = e.toObject()
218+
if (isRecord(obj.data)) {
219+
for (const [key, value] of Object.entries(obj.data)) {
220+
if (key === "name" || key === "stack" || key === "cause") continue
221+
data[key] = value
222+
}
223+
}
224+
}
225+
222226
if (e instanceof ResolveMessage) {
223227
Object.assign(data, {
224228
name: e.name,

packages/opencode/src/lsp/client.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types
77
import * as Log from "@opencode-ai/core/util/log"
88
import { Process } from "@/util/process"
99
import { LANGUAGE_EXTENSIONS } from "./language"
10-
import z from "zod"
1110
import { Schema } from "effect"
1211
import type * as LSPServer from "./server"
1312
import { NamedError } from "@opencode-ai/core/util/error"
@@ -32,12 +31,9 @@ export type Info = NonNullable<Awaited<ReturnType<typeof create>>>
3231

3332
export type Diagnostic = VSCodeDiagnostic
3433

35-
export const InitializeError = NamedError.create(
36-
"LSPInitializeError",
37-
z.object({
38-
serverID: z.string(),
39-
}),
40-
)
34+
export const InitializeError = NamedError.create("LSPInitializeError", {
35+
serverID: Schema.String,
36+
})
4137

4238
export const Event = {
4339
Diagnostics: BusEvent.define(

0 commit comments

Comments
 (0)