shelving
Version:
Toolkit for using data in JavaScript.
53 lines (52 loc) • 3.1 kB
JavaScript
import { NotFoundError, RequestError } from "../error/RequestError.js";
import { getDictionary } from "../util/dictionary.js";
import { getRequestContent } from "../util/http.js";
import { isPlainObject } from "../util/object.js";
import { matchTemplate } from "../util/template.js";
import { getURL } from "../util/url.js";
/**
* Handler a `Request` with the first matching `EndpointHandlers`.
*
* 1. Define your `Endpoint` objects with a method, path, payload and result validators, e.g. `GET("/test/{id}", PAYLOAD, STRING)`
* 2. Make an array of `EndpointHandler` objects combining an `Endpoint` with a `callback` function
* -
*
* @returns The resulting `Response` from the first handler that matches the `Request`.
* @throws `NotFoundError` if no handler matches the `Request`.
*/
export function handleEndpoints(request, endpoints) {
// Parse the URL of the request.
const requestUrl = request.url;
const url = getURL(requestUrl);
if (!url)
throw new RequestError("Invalid request URL", { received: requestUrl, caller: handleEndpoints });
const { pathname, searchParams } = url;
// Iterate over the handlers and return the first one that matches the request.
for (const { endpoint, callback } of endpoints) {
// Ensure the request method e.g. `GET`, does not match the endpoint method e.g. `POST`
if (request.method !== endpoint.method)
continue;
// Ensure the request URL e.g. `/user/123` matches the endpoint path e.g. `/user/{id}`
// Any `{placeholders}` in the endpoint path are matched against the request URL to extract parameters.
const pathParams = matchTemplate(endpoint.path, pathname, handleEndpoints);
if (!pathParams)
continue;
// Make a simple dictionary object from the `{placeholder}` path params and the `?a=123` query params from the URL.
const params = searchParams.size ? { ...getDictionary(searchParams), ...pathParams } : pathParams;
// Get the response by calling the callback.
return handleEndpoint(endpoint, callback, params, request);
}
// No handler matched the request.
throw new NotFoundError("No matching endpoint", { received: requestUrl, caller: handleEndpoints });
}
/** Handle an individual call to an endpoint callback. */
async function handleEndpoint(endpoint, callback, params, request) {
// Extract a data object from the request body and validate it against the endpoint's payload type.
const content = await getRequestContent(request, handleEndpoints);
// If content is undefined, it means the request has no body, so params are the only payload.
// - If the content is a plain object, merge if with the params.
// - If the content is anything else (e.g. string, number, array), set it as a single `content` property.
const payload = content === undefined ? params : isPlainObject(content) ? { ...content, ...params } : { content, ...params };
// Call `endpoint.handle()` with the payload and request.
return endpoint.handle(callback, payload, request);
}