UNPKG

memserver

Version:

in-memory database/ORM and http mock server you can run in-browser and node environments. Built for large frontend teams, fast tests and rapid prototyping

236 lines (205 loc) 8.37 kB
import kleur from "kleur"; import { singularize } from "inflected"; import { classify } from "@emberx/string"; import Model from "@memserver/model"; export default function hackPretender(Pretender) { // HACK START: Pretender Request Parameter Type Casting Hack: Because types are important. Pretender.prototype._handlerFor = function (verb, url, request) { var registry = this.hosts.forURL(url)[verb]; // @ts-ignore var matches = registry.recognize(Pretender.parseURL(url).fullpath); var match = matches ? matches[0] : null; var headers = request.requestHeaders || {}; if (match) { request.headers = headers; request.params = Object.keys(match.params).reduce((result, key) => { var value = castCorrectType(match.params[key]); return Object.assign(result, { [key]: value }); }, {}); request.queryParams = Object.keys(matches.queryParams).reduce((result, key) => { var targetValue = castCorrectType(matches.queryParams[key]); return Object.assign(result, { [key]: targetValue }); }, {}); let newParamsFromBody = tryConvertingJSONStringToObject(request.requestBody) || tryConvertingQueryStringToObject(request.requestBody); if (newParamsFromBody) { request.params = nilifyStrings(Object.assign(request.params, newParamsFromBody)); } } return match; }; function castCorrectType(value) { if (Array.isArray(value)) { return value.map(element => castCorrectType(element)); } else if (Number(value) && parseInt(value, 10)) { return Number(value); } else if (value === "false") { return false; } else if (value === "true") { return true; } return nilifyStrings(value); } function tryConvertingJSONStringToObject(string) { try { let object = JSON.parse(string); if (typeof object === "object" && object !== null) { return object; } } catch (error) {} } function tryConvertingQueryStringToObject(queryString) { let entries = Array.from(new URLSearchParams(queryString)); if (entries.length > 0) { return entries.reduce((result, entry) => { result[entry[0]] = entry[1]; return result; }, {}); } } function nilifyStrings(value) { if (value !== null && typeof value === "object") { return Object.keys(value).reduce((object, key) => { return Object.assign(object, { [key]: nilifyStrings(value[key]) }); }, {}); } else if (value === "") { return null; } return value; } // END: Pretender Request Parameter Type Casting Hack // HACK START: Pretender Response Defaults UX Hack: Because Pretender Response types suck UX-wise. Pretender.prototype.handleRequest = function (request) { var pretender = this; var verb = request.method.toUpperCase(); var path = request.url; var handler = pretender._handlerFor(verb, path, request); var _handleRequest = function (result) { var statusCode, headers, body; if (Array.isArray(result) && result.length === 3) { statusCode = result[0]; headers = pretender.prepareHeaders(result[1]); body = pretender.prepareBody(result[2], headers); return pretender.handleResponse(request, async, function () { request.respond(statusCode, headers, body); pretender.handledRequest(verb, path, request); }); } else if (!result) { headers = pretender.prepareHeaders({ "Content-Type": "application/json" }); if (verb === "DELETE") { return pretender.handleResponse(request, async, function () { request.respond(204, headers, pretender.prepareBody("{}", headers)); pretender.handledRequest(verb, path, request); }); } return pretender.handleResponse(request, async, function () { request.respond(500, headers, pretender.prepareBody(JSON.stringify({ error: `[Memserver] ${verb} ${path} route handler did not return anything to respond to the request!` }), headers)); pretender.handledRequest(verb, path, request); }); } statusCode = getDefaultStatusCode(verb); headers = pretender.prepareHeaders({ "Content-Type": "application/json" }); var targetResult = typeof result === "string" ? result : JSON.stringify(result); body = pretender.prepareBody(targetResult, headers); return pretender.handleResponse(request, async, function () { request.respond(statusCode, headers, body); pretender.handledRequest(verb, path, request); }); }; if (handler) { var async = handler.handler.async; handler.handler.numberOfCalls++; this.handledRequests.push(request); var result = handler.handler(request); if (result && typeof result.then === "function") { // `result` is a promise, resolve it result.then(function (resolvedResult) { _handleRequest(resolvedResult); }); } else { _handleRequest(result); } } else { if (!this.disableUnhandled) { this.unhandledRequests.push(request); this.unhandledRequest(verb, path, request); } } }; function getDefaultStatusCode(verb) { if (["GET", "PUT", "PATCH"].includes(verb)) { return 200; } else if (verb === "POST") { return 201; } else if (verb === "DELETE") { return 204; } return 500; } // END: Pretender Response Defaults UX Hack // HACK: Pretender REST defaults hack: For better UX ["get", "put", "post", "delete"].forEach(verb => { Pretender.prototype[verb] = function (path, handler, async) { const fullPath = (this.urlPrefix || "") + (this.namespace ? "/" + this.namespace : "") + path; const MemServerModel = window.MemserverModel || Model; const defaultResourceDefinition = MemServerModel.isPrototypeOf(handler) ? handler : null; const targetHandler = handler || getDefaultRouteHandler(verb.toUpperCase(), fullPath, this, defaultResourceDefinition); const timing = async ? async.timing || this.timing : this.timing; return this.register(verb.toUpperCase(), fullPath, targetHandler, timing); }; }); // END: Pretender REST default hack: For better UX function getDefaultRouteHandler(verb, path, serverContext, defaultResourceDefinition) { const paths = path.split(/\//g); const lastPath = paths[paths.length - 1]; const pluralResourceName = lastPath.includes(":") ? paths[paths.length - 2] : lastPath; const resourceName = singularize(pluralResourceName); const resourceClassName = classify(resourceName); const ResourceModel = defaultResourceDefinition || Model._modelDefinitions[resourceClassName] || serverContext.Models[resourceClassName]; if (!ResourceModel) { serverContext.shutdown(); throw new Error(kleur.red(`[Memserver] ${verb} ${path} route handler cannot be generated automatically: ${classify(resourceName)} is not on your window.${classify(resourceName)}, also please check that your route name matches the model reference or create a custom handler function`)); } else if (verb === "GET") { if (lastPath.includes(":")) { return request => { return { [resourceName]: ResourceModel.serializer(ResourceModel.find(request.params.id)) }; }; } return () => { return { [pluralResourceName]: ResourceModel.serializer(ResourceModel.findAll()) }; }; } else if (verb === "POST") { return request => { const resourceParams = request.params[resourceName]; return { [resourceName]: ResourceModel.serializer(ResourceModel.insert(resourceParams)) }; }; } else if (verb === "PUT") { return request => { const resourceParams = request.params[resourceName]; return { [resourceName]: ResourceModel.serializer(ResourceModel.update(resourceParams)) }; }; } else if (verb === "DELETE") { return request => { ResourceModel.delete({ id: request.params.id }); }; } } // console.log("PRETENDER HACKS END"); // return Pretender; }