counterfact
Version:
a library for building a fake REST API for testing
110 lines (109 loc) • 3.98 kB
JavaScript
import createDebugger from "debug";
import { ModuleTree } from "./module-tree.js";
const debug = createDebugger("counterfact:server:registry");
function castParameter(value, type) {
if (typeof value !== "string") {
return value;
}
if (type === "integer") {
return Number.parseInt(value);
}
if (type === "number") {
return Number.parseFloat(value);
}
if (type === "boolean") {
return value === "true";
}
return value;
}
function castParameters(parameters = {}, parameterTypes = {}) {
const copy = {};
Object.entries(parameters).forEach(([key, value]) => {
copy[key] = castParameter(value, parameterTypes?.[key] ?? "string");
});
return copy;
}
export class Registry {
moduleTree = new ModuleTree();
middlewares = new Map();
constructor() {
this.middlewares.set("/", ($, respondTo) => respondTo($));
}
get routes() {
return this.moduleTree.routes;
}
add(url, module) {
this.moduleTree.add(url, module);
}
addMiddleware(url, callback) {
this.middlewares.set(url, callback);
}
remove(url) {
this.moduleTree.remove(url);
}
exists(method, url) {
return Boolean(this.handler(url, method).module?.[method]);
}
handler(url, method) {
const match = this.moduleTree.match(url, method);
return {
matchedPath: match?.matchedPath ?? "",
module: match?.module,
path: match?.pathVariables ?? {},
};
}
endpoint(httpRequestMethod, url, parameterTypes = {}) {
const handler = this.handler(url, httpRequestMethod);
debug("handler for %s: %o", url, handler);
const execute = handler.module?.[httpRequestMethod];
if (!execute) {
return () => ({
body: `Could not find a ${httpRequestMethod} method matching ${url}\n`,
contentType: "text/plain",
headers: {},
status: 404,
});
}
return async ({ ...requestData }) => {
const operationArgument = {
...requestData,
headers: castParameters(requestData.headers, parameterTypes.header),
matchedPath: handler.matchedPath,
path: castParameters(handler.path, parameterTypes.path),
query: castParameters(requestData.query, parameterTypes.query),
};
operationArgument.x = operationArgument;
const executeAndNormalizeResponse = async (requestData) => {
const result = await execute(requestData);
if (typeof result === "string") {
return {
headers: {},
status: 200,
body: result,
contentType: "text/plain",
};
}
if (typeof result === "undefined") {
return {
headers: {},
body: `The ${httpRequestMethod} function did not return anything. Did you forget a return statement?`,
status: 500,
};
}
return result;
};
const middlewares = this.middlewares;
function recurse(path, respondTo) {
if (path === null)
return respondTo;
const nextPath = path === "" ? null : path.slice(0, path.lastIndexOf("/"));
const middleware = middlewares.get(path);
if (middleware !== undefined) {
return recurse(nextPath, ($) => middleware($, respondTo));
}
return recurse(nextPath, respondTo);
}
return recurse(operationArgument.matchedPath ?? "/", executeAndNormalizeResponse)(operationArgument);
};
}
}