Skip to content
File

Blob: worker/src/server/http.ts

typescript49 lines
1import { demand, OperationError } from './rpc.ts';
2import type { RpcResult } from './rpc.ts';
3import type { z } from 'zod';
4 
5export type HttpApp = { Bindings: Env; Variables: { requestId: string } };
6 
7export function parse<T>(schema: z.ZodType<T>, value: unknown, message = 'Invalid request.'): T {
8 const result = schema.safeParse(value);
9 // Zod issues may contain sensitive input: never send them to a log or client.
10 if (!result.success) throw new OperationError(400, message);
11 return result.data;
12}
13export async function body(request: Request): Promise<unknown> {
14 const reader = request.body?.getReader();
15 demand(reader, 400, 'Expected a JSON request.');
16 const chunks: Uint8Array[] = [];
17 let length = 0;
18 for (;;) {
19 const { value, done } = await reader.read();
20 if (done) break;
21 length += value.byteLength;
22 if (length > 20000) {
23 await reader.cancel();
24 throw new OperationError(413, 'Request too large.');
25 }
26 chunks.push(value);
27 }
28 const bytes = new Uint8Array(length);
29 let offset = 0;
30 for (const chunk of chunks) {
31 bytes.set(chunk, offset);
32 offset += chunk.byteLength;
33 }
34 try {
35 return JSON.parse(new TextDecoder().decode(bytes));
36 } catch {
37 throw new OperationError(400, 'Invalid JSON request.');
38 }
39}
40export function json(value: unknown, status = 200, headers: Record<string, string> = {}): Response {
41 return Response.json(value, { status, headers: { 'Cache-Control': 'no-store', ...headers } });
42}
43 
44export function rpcResponse<T>(result: RpcResult<T>, requestId: string): Response {
45 return result.ok
46 ? json(result.value, 200, { 'X-Request-Id': requestId })
47 : json({ error: result.error, requestId }, result.status);
48}