Skip to content

Commit b585825

Browse files
committed
Handle SDK errors, optional image, and defects
1 parent c7b1d54 commit b585825

4 files changed

Lines changed: 37 additions & 10 deletions

File tree

apps/admin/src/app/api/simulate/sandbox/route.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ import { NextResponse } from 'next/server'
22
import type { ProfileName } from '@sandchest/contract'
33
import { createClient } from '@/lib/simulate-sdk'
44

5+
function sdkErrorStatus(err: unknown): number {
6+
if (typeof err === 'object' && err !== null && 'status' in err) {
7+
const s = (err as { status: number }).status
8+
if (s >= 400 && s < 600) return s
9+
}
10+
return 500
11+
}
12+
13+
function sdkErrorMessage(err: unknown): string {
14+
if (err instanceof Error) return err.message
15+
return 'Unknown error'
16+
}
17+
518
export async function POST(request: Request) {
619
try {
720
const body = (await request.json()) as {
@@ -17,6 +30,7 @@ export async function POST(request: Request) {
1730
image: body.image || undefined,
1831
profile: (body.profile as ProfileName) || undefined,
1932
ttlSeconds: body.ttlSeconds || undefined,
33+
waitReady: false,
2034
})
2135

2236
return NextResponse.json({
@@ -25,8 +39,10 @@ export async function POST(request: Request) {
2539
replayUrl: sandbox.replayUrl,
2640
})
2741
} catch (err) {
28-
const message = err instanceof Error ? err.message : 'Unknown error'
29-
return NextResponse.json({ error: message }, { status: 500 })
42+
return NextResponse.json(
43+
{ error: sdkErrorMessage(err) },
44+
{ status: sdkErrorStatus(err) },
45+
)
3046
}
3147
}
3248

@@ -44,7 +60,9 @@ export async function DELETE(request: Request) {
4460

4561
return NextResponse.json({ ok: true })
4662
} catch (err) {
47-
const message = err instanceof Error ? err.message : 'Unknown error'
48-
return NextResponse.json({ error: message }, { status: 500 })
63+
return NextResponse.json(
64+
{ error: sdkErrorMessage(err) },
65+
{ status: sdkErrorStatus(err) },
66+
)
4967
}
5068
}

apps/admin/src/app/simulate/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function SimulatePage() {
5353
setActiveSandboxId(null)
5454
}
5555

56-
async function handleCreateSandbox(opts: { image: string; profile: string; ttlSeconds: number }) {
56+
async function handleCreateSandbox(opts: { image: string | undefined; profile: string; ttlSeconds: number }) {
5757
const result = await createSandbox.mutateAsync({
5858
...creds,
5959
image: opts.image,

apps/admin/src/components/simulate/SandboxInventory.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface SandboxInventoryProps {
1313
sandboxes: TrackedSandbox[]
1414
activeSandboxId: string | null
1515
onSelect: (id: string) => void
16-
onCreateSandbox: (opts: { image: string; profile: string; ttlSeconds: number }) => void
16+
onCreateSandbox: (opts: { image: string | undefined; profile: string; ttlSeconds: number }) => void
1717
onStopSandbox: (id: string) => void
1818
onDestroySandbox: (id: string) => void
1919
onForkSandbox: (id: string) => void
@@ -40,7 +40,7 @@ export default function SandboxInventory({
4040
function handleCreate(e: React.FormEvent) {
4141
e.preventDefault()
4242
onCreateSandbox({
43-
image: image || 'default',
43+
image: image || undefined,
4444
profile,
4545
ttlSeconds: parseInt(ttl, 10) || 3600,
4646
})
@@ -68,7 +68,7 @@ export default function SandboxInventory({
6868
className="form-input"
6969
value={image}
7070
onChange={(e) => setImage(e.target.value)}
71-
placeholder="Image (default)"
71+
placeholder="Image (ubuntu-22.04)"
7272
style={{ fontSize: '0.75rem', padding: '0.25rem 0.5rem' }}
7373
/>
7474
<select

apps/api/src/server.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from '@effect/platform'
1+
import { HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from '@effect/platform'
22
import { Effect } from 'effect'
33
import { auth } from './auth.js'
44
import { formatApiError } from './errors.js'
@@ -42,4 +42,13 @@ export const ApiRouter = HttpRouter.empty.pipe(
4242
HttpRouter.catchAll((error) => Effect.succeed(formatApiError(error))),
4343
)
4444

45-
export const AppLive = ApiRouter.pipe(withConnectionDrain, withRateLimit, withRequestId, withSecurityHeaders, HttpServer.serve())
45+
/** Catches Effect defects (unexpected errors) and returns a proper JSON 500 response. */
46+
const withDefectHandler = HttpMiddleware.make((app) =>
47+
app.pipe(
48+
Effect.catchAllDefect(() =>
49+
Effect.succeed(formatApiError(new Error('internal defect'))),
50+
),
51+
),
52+
)
53+
54+
export const AppLive = ApiRouter.pipe(withDefectHandler, withConnectionDrain, withRateLimit, withRequestId, withSecurityHeaders, HttpServer.serve())

0 commit comments

Comments
 (0)