/// import type { Request as CloudflareRequest } from "@cloudflare/workers-types" import { Method } from "../Method" import * as Parser from "../Parser" import * as Serializer from "../Serializer" import { Header as RequestHeader } from "./Header" import { Like as RequestLike } from "./Like" export interface Request { readonly method: Method readonly url: URL readonly parameter: { readonly [key: string]: string | undefined } readonly search: { readonly [key: string]: string | undefined } readonly remote?: string readonly header: Readonly readonly body?: any | Promise readonly cloudflare?: Readonly } export namespace Request { export function is(value: any | Request): value is Request { return ( typeof value == "object" && Object.keys(value).every(key => ["method", "url", "parameter", "search", "remote", "header", "body"].some(k => k == key) ) && Method.is(value.method) && value.url instanceof URL && typeof value.parameter == "object" && Object.entries(value.parameter).every( ([parameter, value]) => typeof parameter == "string" && typeof value == "string" ) && typeof value.search == "object" && Object.entries(value.search).every(([key, value]) => typeof key == "string" && typeof value == "string") && (value.remote == undefined || typeof value.remote == "string") && (value.header == undefined || RequestHeader.is(value.header)) ) } export async function to(request: RequestLike): Promise { const r = is(request) ? request : create(request) // If what is being sent is multipart/form-data, its previous content-type header // needs to be removed in order for the new form-data boundary to be set correctly. const header = r.header.contentType?.startsWith("multipart/form-data") ? (({ contentType, ...header }: Request.Header) => header)(r.header) : r.header return { url: r.url.toString(), method: r.method, headers: RequestHeader.to(header) as Record, body: ["GET", "HEAD"].some(v => v == r.method) ? undefined : await Serializer.serialize(await r.body, r.header.contentType), } } export function from(request: globalThis.Request | CloudflareRequest): Request { const url = new URL(request.url) return { method: Method.parse(request.method) ?? "GET", url, header: RequestHeader.from(request.headers), parameter: {}, search: Object.fromEntries(url.searchParams.entries()), body: Parser.parse(request), ...("cf" in request && { cloudflare: request.cf }), } } export function create(request: string | RequestLike): Request { let result: Request if (typeof request == "string") result = create({ url: request }) else { const url = typeof request.url == "string" ? new URL(request.url) : request.url result = { method: Method.parse(request.method) ?? "GET", url, parameter: request.parameter ?? {}, search: { ...request.search, ...Object.fromEntries(url.searchParams.entries()) }, remote: request.remote, header: request.header ?? {}, body: request.body, } } return result } export type Header = RequestHeader export namespace Header { export const is = RequestHeader.is export const from = RequestHeader.from export const to = RequestHeader.to } export type Like = RequestLike }