fix(httpapi): return session busy error bodies (#28684)
This commit is contained in:
@@ -19,7 +19,7 @@ import {
|
|||||||
WorkspaceRoutingQuery,
|
WorkspaceRoutingQuery,
|
||||||
WorkspaceRoutingQueryFields,
|
WorkspaceRoutingQueryFields,
|
||||||
} from "../middleware/workspace-routing"
|
} from "../middleware/workspace-routing"
|
||||||
import { ApiNotFoundError } from "../errors"
|
import { ApiNotFoundError, SessionBusyError } from "../errors"
|
||||||
import { described } from "./metadata"
|
import { described } from "./metadata"
|
||||||
import { QueryBoolean } from "./query"
|
import { QueryBoolean } from "./query"
|
||||||
|
|
||||||
@@ -354,7 +354,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: ShellPayload,
|
payload: ShellPayload,
|
||||||
success: described(MessageV2.WithParts, "Created message"),
|
success: described(MessageV2.WithParts, "Created message"),
|
||||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.shell",
|
identifier: "session.shell",
|
||||||
@@ -367,7 +367,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: RevertPayload,
|
payload: RevertPayload,
|
||||||
success: described(Session.Info, "Updated session"),
|
success: described(Session.Info, "Updated session"),
|
||||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.revert",
|
identifier: "session.revert",
|
||||||
@@ -380,7 +380,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Session.Info, "Updated session"),
|
success: described(Session.Info, "Updated session"),
|
||||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.unrevert",
|
identifier: "session.unrevert",
|
||||||
@@ -406,7 +406,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID, messageID: MessageID },
|
params: { sessionID: SessionID, messageID: MessageID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Schema.Boolean, "Successfully deleted message"),
|
success: described(Schema.Boolean, "Successfully deleted message"),
|
||||||
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.deleteMessage",
|
identifier: "session.deleteMessage",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { NotFoundError as StorageNotFoundError } from "@/storage/storage"
|
import type { NotFoundError as StorageNotFoundError } from "@/storage/storage"
|
||||||
import type { Session } from "@/session/session"
|
import type { Session } from "@/session/session"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { HttpApiError } from "effect/unstable/httpapi"
|
|
||||||
import * as ApiError from "../errors"
|
import * as ApiError from "../errors"
|
||||||
|
|
||||||
export function mapStorageNotFound<A, R>(self: Effect.Effect<A, StorageNotFoundError, R>) {
|
export function mapStorageNotFound<A, R>(self: Effect.Effect<A, StorageNotFoundError, R>) {
|
||||||
@@ -9,5 +8,14 @@ export function mapStorageNotFound<A, R>(self: Effect.Effect<A, StorageNotFoundE
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function mapBusy<A, R>(self: Effect.Effect<A, Session.BusyError, R>) {
|
export function mapBusy<A, R>(self: Effect.Effect<A, Session.BusyError, R>) {
|
||||||
return self.pipe(Effect.catchTag("SessionBusyError", () => Effect.fail(new HttpApiError.BadRequest({}))))
|
return self.pipe(
|
||||||
|
Effect.catchTag("SessionBusyError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new ApiError.SessionBusyError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session is busy: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,4 +142,19 @@ describe("PublicApi OpenAPI v2 errors", () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("documents session busy errors", () => {
|
||||||
|
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
||||||
|
|
||||||
|
for (const route of [
|
||||||
|
["post", "/session/{sessionID}/shell"],
|
||||||
|
["post", "/session/{sessionID}/revert"],
|
||||||
|
["post", "/session/{sessionID}/unrevert"],
|
||||||
|
["delete", "/session/{sessionID}/message/{messageID}"],
|
||||||
|
] as const) {
|
||||||
|
expect(componentName(responseRef(spec.paths[route[1]]?.[route[0]]?.responses?.["409"]) ?? "")).toBe(
|
||||||
|
"SessionBusyError",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { afterEach, describe, expect } from "bun:test"
|
import { afterEach, describe, expect } from "bun:test"
|
||||||
import { mkdir } from "node:fs/promises"
|
import { mkdir } from "node:fs/promises"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import { Effect, Layer } from "effect"
|
import { Cause, Effect, Exit, Layer } from "effect"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { registerAdapter } from "../../src/control-plane/adapters"
|
import { registerAdapter } from "../../src/control-plane/adapters"
|
||||||
import type { WorkspaceAdapter } from "../../src/control-plane/types"
|
import type { WorkspaceAdapter } from "../../src/control-plane/types"
|
||||||
@@ -13,6 +13,7 @@ import { InstanceBootstrap as InstanceBootstrapService } from "../../src/project
|
|||||||
import { InstanceStore } from "../../src/project/instance-store"
|
import { InstanceStore } from "../../src/project/instance-store"
|
||||||
import { Project } from "../../src/project/project"
|
import { Project } from "../../src/project/project"
|
||||||
import { Server } from "../../src/server/server"
|
import { Server } from "../../src/server/server"
|
||||||
|
import * as HttpSessionError from "../../src/server/routes/instance/httpapi/handlers/session-errors"
|
||||||
import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
|
import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
import { MessageID, PartID, SessionID, type SessionID as SessionIDType } from "../../src/session/schema"
|
import { MessageID, PartID, SessionID, type SessionID as SessionIDType } from "../../src/session/schema"
|
||||||
@@ -215,6 +216,22 @@ afterEach(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("session HttpApi", () => {
|
describe("session HttpApi", () => {
|
||||||
|
it.effect("maps busy sessions to public session busy errors", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const sessionID = SessionID.descending()
|
||||||
|
const exit = yield* HttpSessionError.mapBusy(Effect.fail(new Session.BusyError({ sessionID }))).pipe(Effect.exit)
|
||||||
|
|
||||||
|
expect(Exit.isFailure(exit)).toBe(true)
|
||||||
|
if (Exit.isFailure(exit)) {
|
||||||
|
expect(Cause.squash(exit.cause)).toMatchObject({
|
||||||
|
_tag: "SessionBusyError",
|
||||||
|
sessionID,
|
||||||
|
message: `Session is busy: ${sessionID}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.instance(
|
it.instance(
|
||||||
"returns declared not found errors for read routes",
|
"returns declared not found errors for read routes",
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user