UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

53 lines (52 loc) 3.1 kB
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); }