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
185 lines (154 loc) • 6.72 kB
JavaScript
import qs from 'qs';
import chalk from 'chalk';
import Inflector from 'i';
import { classify } from 'ember-cli-string-utils';
const { singularize } = Inflector();
const targetNamespace = typeof global === 'object' ? global : window;
// HACK START: Pretender Request Parameter Type Casting Hack: Because types are important.
window.Pretender.prototype._handlerFor = function(verb, url, request) {
var registry = this.hosts.forURL(url)[verb];
var matches = registry.recognize(window.Pretender.parseURL(url).fullpath);
var match = matches ? matches[0] : null;
if (match) {
request.headers = request.requestHeaders;
request.params = Object.keys(match.params).reduce((result, key) => {
var value = match.params[key];
return Object.assign(result, { [key]: parseInt(value, 10) || value });
}, {});
request.queryParams = Object.keys(matches.queryParams).reduce((result, key) => {
var value = matches.queryParams[key];
var targetValue = castCorrectType(value);
return Object.assign(result, { [key]: targetValue });
}, {});
if (request.requestBody && request.requestHeaders['Content-Type'] === 'application/json') {
request.params = Object.assign(request.params, JSON.parse(request.requestBody));
} else {
request.params = Object.assign(request.params, qs.parse(request.requestBody ));
}
}
return match;
};
function castCorrectType(value) {
if (Array.isArray(value)) {
return value.map((element) => castCorrectType(element));
} else if (parseInt(value, 10)) {
return parseInt(value, 10);
} else if (value === 'false') {
return false;
} else if (value === 'true') {
return true;
}
return value;
}
// END: Pretender Request Parameter Type Casting Hack
// HACK START: Pretender Response Defaults UX Hack: Because Pretender Response types suck UX-wise.
window.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) => {
window.Pretender.prototype[verb] = function (path, handler, async) {
const fullPath = (this.urlPrefix || '') + (this.namespace ? ('/' + this.namespace) : '') + path;
const targetHandler = handler || getDefaultRouteHandler(verb.toUpperCase(), fullPath);
const timing = async ? async.timing || this.timing : this.timing;
// console.log('timing is', timing);
// console.log('async is', async);
return this.register(verb.toUpperCase(), fullPath, targetHandler, timing);
};
});
// END: Pretender REST default hack: For better UX
function getDefaultRouteHandler(verb, path) {
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 ResourceModel = targetNamespace.MemServer.Models[classify(resourceName)];
if (!ResourceModel) {
throw new Error(chalk.red(`[MemServer] ${verb} ${path} route handler cannot be generated automatically: ${classify(resourceName)} is not a valid MemServer.Model, 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 }); };
}
}