export type ParamDict = Record; export type Headers = Record; function splitUrl(url: string) { return url.split('/').map(v => v.trim()).filter(v => v !== ''); } function sanitizeUrl(url: string, forceAbsolute = true) { url = url.trim(); if (forceAbsolute || url.startsWith('/')) { return '/' + splitUrl(url).join('/'); } else { return '/' + splitUrl(url).join('/'); } } export default class RestRequest { public readonly body: unknown; public readonly method: string; public readonly url: string; public readonly pathParams: ParamDict; public readonly queryParams: ParamDict; public readonly headers: Headers; public get params() { return { ...this.queryParams, ...this.pathParams }; } public constructor( body: unknown, headers: Headers, method: string, url: string, pathParams: ParamDict = {}, queryParams: ParamDict = {} ) { this.body = body; this.headers = headers; this.pathParams = { ...pathParams }; this.queryParams = { ...queryParams }; this.method = method.toLowerCase(); this.url = sanitizeUrl(url); if (this.url.includes('?')) { const questionIndex = this.url.indexOf('?'); this.url = this.url.substring(0, questionIndex); const params = this.url .substring(questionIndex + 1) .split('&') .map(v => v.trim()) .filter(v => v !== ''); for (const rawParam of params) { const i = rawParam.indexOf('='); if (i < 0) continue; const name = rawParam.substring(0, i); const val = rawParam.substring(i + 1); if (name === '') continue; this.queryParams[name] = val; } } } public match(predicate: string) { const urlSegments = splitUrl(this.url); const predSegments = splitUrl(predicate); const wildcardIndex = predSegments.indexOf('*'); const hasWildcard = wildcardIndex >= 0; const pathParams: ParamDict = { ...this.pathParams }; if (wildcardIndex >= 0) { if (predSegments.includes('*', wildcardIndex + 1)) throw new Error("A path predicate may not have more than one wildcard."); if (predSegments.splice(wildcardIndex).length > 1) throw new Error("A path predicate must be the last segment."); } for (const predSeg of predSegments) { const urlSeg = urlSegments.shift(); if (urlSeg === undefined) return undefined; else if (predSeg.startsWith(':')) { const name = predSeg.substring(1); if (name.length === 0) throw new Error('Invalid path predicate - a segment may not be ":".'); pathParams[name] = decodeURI(urlSeg); } else if (predSeg === urlSeg) continue; else return undefined; } if (!hasWildcard && urlSegments.length > 0) return undefined; return new RestRequest( this.body, this.headers, this.method, '/' + urlSegments.join('/'), { ...this.pathParams, ...pathParams }, this.queryParams ); } public static fromMessage(msg: Deno.RequestEvent) { const raw = msg.request.body; const headers = {} as Headers; for (const entry of msg.request.headers.entries()) { headers[entry[0]] = entry[1]; } const url = new URL(msg.request.url); const params = {} as ParamDict; for (const entry of url.searchParams.entries()) params[entry[0]] = entry[1]; return new RestRequest(raw, headers, msg.request.method, url.pathname, {}, params); } }