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
JavaScript
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;
}