mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-13 15:44:56 +00:00
fix(storage): type not found errors (#27265)
This commit is contained in:
@@ -2,8 +2,6 @@ import type { NotFoundError as StorageNotFoundError } from "@/storage/storage"
|
||||
import { Effect } from "effect"
|
||||
import * as ApiError from "../errors"
|
||||
|
||||
type StorageNotFound = InstanceType<typeof StorageNotFoundError>
|
||||
|
||||
export function mapStorageNotFound<A, R>(self: Effect.Effect<A, StorageNotFound, R>) {
|
||||
return self.pipe(Effect.mapError((error) => ApiError.notFound(error.data.message)))
|
||||
export function mapStorageNotFound<A, R>(self: Effect.Effect<A, StorageNotFoundError, R>) {
|
||||
return self.pipe(Effect.mapError((error) => ApiError.notFound(error.message)))
|
||||
}
|
||||
|
||||
@@ -24,11 +24,14 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect)
|
||||
const error = defect.defect
|
||||
log.error("failed", { error, cause: Cause.pretty(cause) })
|
||||
|
||||
if (error instanceof NotFoundError) {
|
||||
return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 404 }))
|
||||
}
|
||||
|
||||
if (error instanceof NamedError) {
|
||||
return Effect.succeed(
|
||||
HttpServerResponse.jsonUnsafe(error.toObject(), {
|
||||
status: iife(() => {
|
||||
if (error instanceof NotFoundError) return 404
|
||||
if (error instanceof Provider.ModelNotFoundError) return 400
|
||||
if (error.name === "ProviderAuthValidationFailed") return 400
|
||||
if (error.name.startsWith("Worktree")) return 400
|
||||
|
||||
@@ -448,7 +448,7 @@ export class BusyError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export type NotFound = InstanceType<typeof NotFoundError>
|
||||
export type NotFound = NotFoundError
|
||||
|
||||
export interface Interface {
|
||||
readonly list: (input?: ListInput) => Effect.Effect<Info[]>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
@@ -15,11 +14,22 @@ type Migration = (
|
||||
git: Git.Interface,
|
||||
) => Effect.Effect<void, AppFileSystem.Error>
|
||||
|
||||
export const NotFoundError = NamedError.create("NotFoundError", {
|
||||
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("NotFoundError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
}) {
|
||||
static isInstance(input: unknown): input is NotFoundError {
|
||||
return input instanceof NotFoundError
|
||||
}
|
||||
|
||||
export type Error = AppFileSystem.Error | InstanceType<typeof NotFoundError>
|
||||
toObject() {
|
||||
return {
|
||||
name: "NotFoundError" as const,
|
||||
data: { message: this.message },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Error = AppFileSystem.Error | NotFoundError
|
||||
|
||||
const RootFile = Schema.Struct({
|
||||
path: Schema.optional(
|
||||
@@ -245,7 +255,7 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
const fail = (target: string): Effect.Effect<never, InstanceType<typeof NotFoundError>> =>
|
||||
const fail = (target: string): Effect.Effect<never, NotFoundError> =>
|
||||
Effect.fail(new NotFoundError({ message: `Resource not found: ${target}` }))
|
||||
|
||||
const wrap = <A>(target: string, body: Effect.Effect<A, AppFileSystem.Error>) =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Session as SessionNs } from "@/session/session"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
import { MessageID, PartID, type SessionID } from "../../src/session/schema"
|
||||
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
@@ -11,6 +12,20 @@ void Log.init({ print: false })
|
||||
|
||||
const it = testEffect(SessionNs.defaultLayer)
|
||||
|
||||
function expectNotFound(fn: () => unknown, message: string) {
|
||||
let thrown: unknown
|
||||
try {
|
||||
fn()
|
||||
} catch (error) {
|
||||
thrown = error
|
||||
}
|
||||
expect(thrown).toBeInstanceOf(NotFoundError)
|
||||
if (thrown instanceof NotFoundError) {
|
||||
expect(thrown._tag).toBe("NotFoundError")
|
||||
expect(thrown.message).toBe(message)
|
||||
}
|
||||
}
|
||||
|
||||
const withSession = <A, E, R>(
|
||||
fn: (input: { session: SessionNs.Interface; sessionID: SessionID }) => Effect.Effect<A, E, R>,
|
||||
) =>
|
||||
@@ -186,7 +201,7 @@ describe("MessageV2.page", () => {
|
||||
it.instance("throws NotFoundError for non-existent session", () =>
|
||||
Effect.gen(function* () {
|
||||
const fake = "non-existent-session" as SessionID
|
||||
expect(() => MessageV2.page({ sessionID: fake, limit: 10 })).toThrow("NotFoundError")
|
||||
expectNotFound(() => MessageV2.page({ sessionID: fake, limit: 10 }), `Session not found: ${fake}`)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -471,7 +486,8 @@ describe("MessageV2.get", () => {
|
||||
it.instance("throws NotFoundError for non-existent message", () =>
|
||||
withSession(({ sessionID }) =>
|
||||
Effect.gen(function* () {
|
||||
expect(() => MessageV2.get({ sessionID, messageID: MessageID.ascending() })).toThrow("NotFoundError")
|
||||
const messageID = MessageID.ascending()
|
||||
expectNotFound(() => MessageV2.get({ sessionID, messageID }), `Message not found: ${messageID}`)
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -483,7 +499,7 @@ describe("MessageV2.get", () => {
|
||||
const b = yield* session.create({})
|
||||
const [id] = yield* fill(a.id, 1)
|
||||
|
||||
expect(() => MessageV2.get({ sessionID: b.id, messageID: id })).toThrow("NotFoundError")
|
||||
expectNotFound(() => MessageV2.get({ sessionID: b.id, messageID: id }), `Message not found: ${id}`)
|
||||
const result = MessageV2.get({ sessionID: a.id, messageID: id })
|
||||
expect(result.info.id).toBe(id)
|
||||
|
||||
|
||||
@@ -74,20 +74,23 @@ describe("Storage", () => {
|
||||
it.live("maps missing reads to NotFoundError", () =>
|
||||
Effect.gen(function* () {
|
||||
const { root, svc } = yield* scope()
|
||||
const exit = yield* svc.read([...root, "missing", "value"]).pipe(Effect.exit)
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
const error = yield* Effect.flip(svc.read([...root, "missing", "value"]))
|
||||
expect(error).toBeInstanceOf(Storage.NotFoundError)
|
||||
expect(error._tag).toBe("NotFoundError")
|
||||
expect(error.message).toContain(path.join(...root, "missing", "value") + ".json")
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("update on missing key throws NotFoundError", () =>
|
||||
Effect.gen(function* () {
|
||||
const { root, svc } = yield* scope()
|
||||
const exit = yield* svc
|
||||
.update<{ value: number }>([...root, "missing", "key"], (draft) => {
|
||||
const error = yield* Effect.flip(
|
||||
svc.update<{ value: number }>([...root, "missing", "key"], (draft) => {
|
||||
draft.value += 1
|
||||
})
|
||||
.pipe(Effect.exit)
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
}),
|
||||
)
|
||||
expect(error).toBeInstanceOf(Storage.NotFoundError)
|
||||
expect(error._tag).toBe("NotFoundError")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user