113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
export type ParamDict = Record<string, string>;
|
|
export type Headers = Record<string, string | string[] | undefined>;
|
|
|
|
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);
|
|
}
|
|
}
|