Coverage

96%
1316
1270
46

/home/pmcnr/work/jsonapi-server/lib/debugging.js

45%
11
5
6
LineHitsSource
1/* @flow weak */
21"use strict";
3
41var debug = require("debug");
5
61function overrideDebugOutputHelper(debugFns, outputFnFactory) {
70 Object.keys(debugFns).filter(function(key) {
80 return (key.substr(0, 2) !== "__");
9 }).forEach(function(key) {
100 if (debugFns[key] instanceof Function) {
110 debugFns[key] = outputFnFactory(debugFns[key].namespace);
120 return null;
13 }
140 return overrideDebugOutputHelper(debugFns[key], outputFnFactory);
15 });
16}
17
181var debugging = module.exports = {
19 handler: {
20 search: debug("jsonApi:handler:search"),
21 find: debug("jsonApi:handler:find"),
22 create: debug("jsonApi:handler:create"),
23 update: debug("jsonApi:handler:update"),
24 delete: debug("jsonApi:handler:delete")
25 },
26 include: debug("jsonApi:include"),
27 filter: debug("jsonApi:filter"),
28 validationInput: debug("jsonApi:validation:input"),
29 validationOutput: debug("jsonApi:validation:output"),
30 validationError: debug("jsonApi:validation:error"),
31 errors: debug("jsonApi:errors"),
32 requestCounter: debug("jsonApi:requestCounter"),
33
34 __overrideDebugOutput: function() {}
35};
36
371debugging.__overrideDebugOutput = overrideDebugOutputHelper.bind(null, debugging);
38

/home/pmcnr/work/jsonapi-server/lib/filter.js

95%
72
69
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var filter = module.exports = { };
4
5
61var FILTER_OPERATORS = ["<", ">", "~", ":"];
71var STRING_ONLY_OPERATORS = ["~", ":"];
8
9
101filter._resourceDoesNotHaveProperty = function(resourceConfig, key) {
1193 if (resourceConfig.attributes[key]) return null;
123 return {
13 status: "403",
14 code: "EFORBIDDEN",
15 title: "Invalid filter",
16 detail: resourceConfig.resource + " do not have attribute or relationship '" + key + "'"
17 };
18};
19
201filter._relationshipIsForeign = function(resourceConfig, key) {
2145 var relationSettings = resourceConfig.attributes[key]._settings;
2289 if (!relationSettings || !relationSettings.__as) return null;
231 return {
24 status: "403",
25 code: "EFORBIDDEN",
26 title: "Invalid filter",
27 detail: "Filter relationship '" + key + "' is a foreign reference and does not exist on " + resourceConfig.resource
28 };
29};
30
311filter._splitElement = function(element) {
3272 if (!element) return null;
3372 if (FILTER_OPERATORS.indexOf(element[0]) !== -1) {
348 return { operator: element[0], value: element.substring(1) };
35 }
3664 return { operator: null, value: element };
37};
38
391filter._stringOnlyOperator = function(operator, attributeConfig) {
40136 if (!operator || !attributeConfig) return null;
418 if (STRING_ONLY_OPERATORS.indexOf(operator) !== -1 && attributeConfig._type !== "string") {
420 return "operator " + operator + " can only be applied to string attributes";
43 }
448 return null;
45};
46
471filter._parseScalarFilterElement = function(attributeConfig, scalarElement) {
4872 if (!scalarElement) return { error: "invalid or empty filter element" };
49
5072 var splitElement = filter._splitElement(scalarElement);
5172 if (!splitElement) return { error: "empty filter" };
52
5372 var error = filter._stringOnlyOperator(splitElement.operator, attributeConfig);
5472 if (error) return { error: error };
55
5672 if (attributeConfig._settings) { // relationship attribute: no further validation
5717 return { result: splitElement };
58 }
59
6055 var validateResult = attributeConfig.validate(splitElement.value);
6155 if (validateResult.error) {
621 return { error: validateResult.error.message };
63 }
64
6554 var validatedElement = { operator: splitElement.operator, value: validateResult.value };
6654 return { result: validatedElement };
67};
68
691filter._parseFilterElementHelper = function(attributeConfig, filterElement) {
7044 if (!filterElement) return { error: "invalid or empty filter element" };
71
7244 var parsedElements = [].concat(filterElement).map(function(scalarElement) {
7372 return filter._parseScalarFilterElement(attributeConfig, scalarElement);
74 });
75
7676 if (parsedElements.length === 1) return parsedElements[0];
77
7812 var errors = parsedElements.reduce(function(combined, element) {
7940 if (!combined) {
8080 if (!element.error) return combined;
810 return [ element.error ];
82 }
830 return combined.concat(element.error);
84 }, null);
85
8612 if (errors) return { error: errors };
87
8812 var results = parsedElements.map(function(element) {
8940 return element.result;
90 });
91
9212 return { result: results };
93};
94
951filter._parseFilterElement = function(attributeName, attributeConfig, filterElement) {
9644 var helperResult = filter._parseFilterElementHelper(attributeConfig, filterElement);
97
9844 if (helperResult.error) {
991 return {
100 error: {
101 status: "403",
102 code: "EFORBIDDEN",
103 title: "Invalid filter",
104 detail: "Filter value for key '" + attributeName + "' is invalid: " + helperResult.error
105 }
106 };
107 }
10843 return { result: helperResult.result };
109};
110
1111filter.parseAndValidate = function(request) {
112120 if (!request.params.filter) return null;
113
11446 var resourceConfig = request.resourceConfig;
115
11646 var processedFilter = { };
11746 var error;
11846 var filterElement;
11946 var parsedFilterElement;
120
12146 for (var key in request.params.filter) {
12249 filterElement = request.params.filter[key];
123
12450 if (!Array.isArray(filterElement) && filterElement instanceof Object) continue; // skip deep filters
125
12648 error = filter._resourceDoesNotHaveProperty(resourceConfig, key);
12751 if (error) return error;
128
12945 error = filter._relationshipIsForeign(resourceConfig, key);
13046 if (error) return error;
131
13244 parsedFilterElement = filter._parseFilterElement(key, resourceConfig.attributes[key], filterElement);
13345 if (parsedFilterElement.error) return parsedFilterElement.error;
134
13543 processedFilter[key] = [].concat(parsedFilterElement.result);
136 }
137
13841 request.processedFilter = processedFilter;
139
14041 return null;
141};
142

/home/pmcnr/work/jsonapi-server/lib/handlerEnforcer.js

97%
36
35
1
LineHitsSource
1/* @flow weak */
21"use strict";
31var handlerEnforcer = module.exports = { };
4
51var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js");
6
71handlerEnforcer.wrap = function(handlers) {
85 handlers.search = handlerEnforcer._search(handlers);
95 handlers.find = handlerEnforcer._find(handlers);
105 handlers.create = handlerEnforcer._create(handlers);
115 handlers.update = handlerEnforcer._update(handlers);
125 handlers.delete = handlerEnforcer._delete(handlers);
13};
14
151handlerEnforcer._wrapHandler = function(handlers, operation, outCount) {
1625 if (typeof outCount !== "number") {
170 throw new Error("Invalid use of handlerEnforcer._wrapHandler!");
18 }
19
2025 var original = handlers[operation];
2126 if (!original) return null;
2224 return function() {
23125 var argsIn = Array.prototype.slice.call(arguments);
24125 var requestParams = argsIn[0].params;
25125 var callback = argsIn.pop();
26125 argsIn.push(function() {
27125 var argsOut = Array.prototype.slice.call(arguments);
28125 argsOut = argsOut.slice(0, outCount);
29 // $FlowFixMe: We've already ruled out any other possible types for outCount?
30125 while (argsOut.length < outCount) {
3112 argsOut.push(null);
32 }
33125 debug.handler[operation](JSON.stringify(requestParams), JSON.stringify(argsOut));
34125 return callback.apply(null, argsOut);
35 });
36125 original.apply(handlers, argsIn);
37 };
38};
39
401handlerEnforcer._search = function(handlers) {
415 return handlerEnforcer._wrapHandler(handlers, "search", 3);
42};
43
441handlerEnforcer._find = function(handlers) {
455 return handlerEnforcer._wrapHandler(handlers, "find", 2);
46};
47
481handlerEnforcer._create = function(handlers) {
495 return handlerEnforcer._wrapHandler(handlers, "create", 2);
50};
51
521handlerEnforcer._update = function(handlers) {
535 return handlerEnforcer._wrapHandler(handlers, "update", 2);
54};
55
561handlerEnforcer._delete = function(handlers) {
575 return handlerEnforcer._wrapHandler(handlers, "delete", 1);
58};
59

/home/pmcnr/work/jsonapi-server/lib/jsonApi.js

96%
56
54
2
LineHitsSource
1/* @flow weak */
21"use strict";
31var jsonApi = module.exports = { };
41jsonApi._version = require(require("path").join(__dirname, "../package.json")).version;
51jsonApi._resources = { };
61jsonApi._apiConfig = { };
7
81var _ = {
9 assign: require("lodash.assign"),
10 pick: require("lodash.pick")
11};
121var ourJoi = require("/home/pmcnr/work/jsonapi-server/lib/./ourJoi.js");
131var router = require("/home/pmcnr/work/jsonapi-server/lib/./router.js");
141var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/./responseHelper.js");
151var handlerEnforcer = require("/home/pmcnr/work/jsonapi-server/lib/./handlerEnforcer.js");
161var pagination = require("/home/pmcnr/work/jsonapi-server/lib/./pagination.js");
171var routes = require("/home/pmcnr/work/jsonapi-server/lib/./routes");
181var url = require("url");
19
201jsonApi.Joi = ourJoi.Joi;
211jsonApi.MemoryHandler = require("/home/pmcnr/work/jsonapi-server/lib/./MemoryHandler");
22
231jsonApi.setConfig = function(apiConfig) {
241 jsonApi._apiConfig = apiConfig;
251 jsonApi._apiConfig.base = jsonApi._cleanBaseUrl(jsonApi._apiConfig.base);
261 jsonApi._apiConfig.pathPrefix = jsonApi._concatenateUrlPrefix(jsonApi._apiConfig);
271 responseHelper.setBaseUrl(jsonApi._apiConfig.pathPrefix);
281 responseHelper.setMetadata(jsonApi._apiConfig.meta);
29};
30
311jsonApi.authenticate = router.authenticateWith;
32
331jsonApi._concatenateUrlPrefix = function(config) {
341 return url.format({
35 protocol: config.protocol,
36 hostname: config.hostname,
37 port: config.port,
38 pathname: config.base
39 });
40};
41
421jsonApi._cleanBaseUrl = function(base) {
431 if (!base) {
440 base = "";
45 }
461 if (base[0] !== "/") {
471 base = "/" + base;
48 }
491 if (base[base.length - 1] !== "/") {
501 base += "/";
51 }
521 return base;
53};
54
551jsonApi.define = function(resourceConfig) {
565 resourceConfig.namespace = resourceConfig.namespace || "default";
575 resourceConfig.searchParams = resourceConfig.searchParams || { };
585 jsonApi._resources[resourceConfig.resource] = resourceConfig;
59
605 handlerEnforcer.wrap(resourceConfig.handlers);
61
625 if (resourceConfig.handlers.initialise) {
635 resourceConfig.handlers.initialise(resourceConfig);
64 }
65
665 Object.keys(resourceConfig.attributes).forEach(function(attribute) {
6729 if (!attribute.match(/^[A-Za-z0-9\-\_]*$/)) {
680 throw new Error("Attribute '" + attribute + "' on " + resourceConfig.resource + " contains illegal characters!");
69 }
70 });
71
725 resourceConfig.searchParams = _.assign({
73 type: ourJoi.Joi.any().required().valid(resourceConfig.resource)
74 .description("Always \"" + resourceConfig.resource + "\"")
75 .example(resourceConfig.resource),
76 sort: ourJoi.Joi.any()
77 .description("An attribute to sort by")
78 .example("title"),
79 filter: ourJoi.Joi.any()
80 .description("An attribute+value to filter by")
81 .example("title"),
82 fields: ourJoi.Joi.any()
83 .description("An attribute+value to filter by")
84 .example("title"),
85 include: ourJoi.Joi.any()
86 .description("An attribute to include")
87 .example("title")
88 }, resourceConfig.searchParams, pagination.joiPageDefinition);
89
905 resourceConfig.attributes = _.assign({
91 id: ourJoi.Joi.string().required()
92 .description("Unique resource identifier")
93 .example("1234"),
94 type: ourJoi.Joi.string().required().valid(resourceConfig.resource)
95 .description("Always \"" + resourceConfig.resource + "\"")
96 .example(resourceConfig.resource),
97 meta: ourJoi.Joi.object().optional()
98 }, resourceConfig.attributes);
99
1005 resourceConfig.onCreate = _.pick.apply(_, [].concat(resourceConfig.attributes, Object.keys(resourceConfig.attributes).filter(function(i) {
10144 return (resourceConfig.attributes[i]._meta.indexOf("readonly") === -1) && (!(resourceConfig.attributes[i]._settings || { }).__as);
102 })));
103};
104
1051jsonApi.onUncaughtException = function(errHandler) {
1061 jsonApi._errHandler = errHandler;
107};
108
1091jsonApi.start = function() {
11020 routes.register();
11120 router.listen(jsonApi._apiConfig.port);
112};
113
1141jsonApi.close = function() {
11519 router.close();
11619 for (var i in jsonApi._resources) {
11795 var resourceConfig = jsonApi._resources[i];
11895 if (resourceConfig.handlers.close) resourceConfig.handlers.close();
119 }
120};
121

/home/pmcnr/work/jsonapi-server/lib/MemoryHandler.js

94%
51
48
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var _ = {
4 assign: require("lodash.assign")
5};
6
71var MemoryStore = module.exports = function MemoryStore() {
8};
9
10// resources represents out in-memory data store
111var resources = { };
12
13/**
14 Handlers readiness status. This should be set to `true` once all handlers are ready to process requests.
15 */
161MemoryStore.prototype.ready = false;
17
18/**
19 initialise gets invoked once for each resource that uses this hander.
20 In this instance, we're allocating an array in our in-memory data store.
21 */
221MemoryStore.prototype.initialise = function(resourceConfig) {
235 resources[resourceConfig.resource] = resourceConfig.examples || [ ];
245 this.ready = true;
25};
26
27/**
28 Search for a list of resources, given a resource type.
29 */
301MemoryStore.prototype.search = function(request, callback) {
3167 var self = this;
32
3367 var results = [].concat(resources[request.params.type]);
3467 self._sortList(request, results);
3567 var resultCount = results.length;
3667 if (request.params.page) {
3766 results = results.slice(request.params.page.offset, request.params.page.offset + request.params.page.limit);
38 }
3967 return callback(null, results, resultCount);
40};
41
42/**
43 Find a specific resource, given a resource type and and id.
44 */
451MemoryStore.prototype.find = function(request, callback) {
46 // Pull the requested resource from the in-memory store
4747 var theResource = resources[request.params.type].filter(function(anyResource) {
48162 return anyResource.id === request.params.id;
49 }).pop();
50
51 // If the resource doesn't exist, error
5247 if (!theResource) {
539 return callback({
54 status: "404",
55 code: "ENOTFOUND",
56 title: "Requested resource does not exist",
57 detail: "There is no " + request.params.type + " with id " + request.params.id
58 });
59 }
60
61 // Return the requested resource
6238 return callback(null, theResource);
63};
64
65/**
66 Create (store) a new resource give a resource type and an object.
67 */
681MemoryStore.prototype.create = function(request, newResource, callback) {
69 // Push the newResource into our in-memory store.
701 resources[request.params.type].push(newResource);
71 // Return the newly created resource
721 return callback(null, newResource);
73};
74
75/**
76 Delete a resource, given a resource type and and id.
77 */
781MemoryStore.prototype.delete = function(request, callback) {
79 // Find the requested resource
802 this.find(request, function(err, theResource) {
813 if (err) return callback(err);
82
83 // Remove the resource from the in-meory store.
841 var resourceIndex = resources[request.params.type].indexOf(theResource);
851 resources[request.params.type].splice(resourceIndex, 1);
86
87 // Return with no error
881 return callback();
89 });
90};
91
92/**
93 Update a resource, given a resource type and id, along with a partialResource.
94 partialResource contains a subset of changes that need to be merged over the original.
95 */
961MemoryStore.prototype.update = function(request, partialResource, callback) {
97 // Find the requested resource
988 this.find(request, function(err, theResource) {
9910 if (err) return callback(err);
100
101 // Merge the partialResource over the original
1026 theResource = _.assign(theResource, partialResource);
103
104 // Push the newly updated resource back into the in-memory store
1056 resources[request.params.type][request.params.id] = theResource;
106
107 // Return the newly updated resource
1086 return callback(null, theResource);
109 });
110};
111
112/**
113 Internal helper function to sort data
114 */
1151MemoryStore.prototype._sortList = function(request, list) {
11667 var attribute = request.params.sort;
117127 if (!attribute) return;
118
1197 var ascending = 1;
1207 attribute = ("" + attribute);
1217 if (attribute[0] === "-") {
1221 ascending = -1;
1231 attribute = attribute.substring(1, attribute.length);
124 }
125
1267 list.sort(function(a, b) {
12729 if (typeof a[attribute] === "string") {
12829 return a[attribute].localeCompare(b[attribute]) * ascending;
1290 } else if (typeof a[attribute] === "number") {
1300 return (a[attribute] - b[attribute]) * ascending;
131 } else {
1320 return 0;
133 }
134 });
135};
136

/home/pmcnr/work/jsonapi-server/lib/ourJoi.js

100%
28
28
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var ourJoi = module.exports = { };
4
5
61var Joi = require("joi");
7
81ourJoi._joiBase = function(resourceName) {
913 var relationType = Joi.object().keys({
10 id: Joi.string().required(),
11 type: Joi.any().required().valid(resourceName),
12 meta: Joi.object().optional()
13 });
1413 return relationType;
15};
161Joi.one = function(resource) {
174 var obj = Joi.alternatives().try(
18 Joi.any().valid(null), // null
19 ourJoi._joiBase(resource)
20 );
214 obj._settings = {
22 __one: resource
23 };
244 return obj;
25};
261Joi.many = function(resource) {
273 var obj = Joi.array().items(ourJoi._joiBase(resource));
283 obj._settings = {
29 __many: resource
30 };
313 return obj;
32};
331Joi._validateForeignRelation = function(config) {
346 if (!config.as) throw new Error("Missing 'as' property when defining a foreign relation");
356 if (!config.resource) throw new Error("Missing 'resource' property when defining a foreign relation");
36};
371Joi.belongsToOne = function(config) {
381 Joi._validateForeignRelation(config);
391 var obj = Joi.alternatives().try(
40 Joi.any().valid(null), // null
41 ourJoi._joiBase(config.resource)
42 );
431 obj._settings = {
44 __one: config.resource,
45 __as: config.as
46 };
471 return obj;
48};
491Joi.belongsToMany = function(config) {
505 Joi._validateForeignRelation(config);
515 var obj = Joi.array().items(ourJoi._joiBase(config.resource));
525 obj._settings = {
53 __many: config.resource,
54 __as: config.as
55 };
565 return obj;
57};
581ourJoi.Joi = Joi;
59

/home/pmcnr/work/jsonapi-server/lib/pagination.js

100%
44
44
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var pagination = module.exports = { };
4
51var ourJoi = require("/home/pmcnr/work/jsonapi-server/lib/./ourJoi.js");
61var url = require("url");
7
8
91pagination.joiPageDefinition = {
10 page: ourJoi.Joi.object().keys({
11 offset: ourJoi.Joi.number()
12 .description("The first record to appear in the resulting payload")
13 .example(0),
14 limit: ourJoi.Joi.number()
15 .description("The number of records to appear in the resulting payload")
16 .example(50)
17 })
18};
19
201pagination.generateMetaSummary = function(request, handlerTotal) {
2166 return {
22 offset: request.params.page.offset,
23 limit: request.params.page.limit,
24 total: handlerTotal
25 };
26};
27
281pagination.validatePaginationParams = function(request) {
2966 if (!request.params.page) {
3061 request.params.page = { };
31 }
3266 var page = request.params.page;
33
3466 page.offset = parseInt(page.offset, 10) || 0;
3566 page.limit = parseInt(page.limit, 10) || 50;
36};
37
381pagination.enforcePagination = function(request, results) {
3966 return results.slice(0, request.params.page.size);
40};
41
421pagination.generatePageLinks = function(request, handlerTotal) {
4393 var pageData = request.params.page;
4493 if (!handlerTotal || !pageData) {
4527 return { };
46 }
47
4866 var lowerLimit = pageData.offset;
4966 var upperLimit = pageData.offset + pageData.limit;
50
5166 if ((lowerLimit === 0) && (upperLimit > handlerTotal)) {
5261 return { };
53 }
54
555 var pageLinks = { };
565 var theirRequest = url.parse(request.route.combined, true);
575 theirRequest.search = null;
58
595 if (lowerLimit > 0) {
604 theirRequest.query["page[offset]"] = 0;
614 pageLinks.first = url.format(theirRequest);
62
634 if (pageData.offset > 0) {
644 var previousPageOffset = pageData.offset - pageData.limit;
654 if (previousPageOffset < 0) {
661 previousPageOffset = 0;
67 }
684 theirRequest.query["page[offset]"] = previousPageOffset;
694 pageLinks.prev = url.format(theirRequest);
70 }
71 }
72
735 if (upperLimit < handlerTotal) {
744 var lastPage = (Math.floor(handlerTotal / pageData.limit) * pageData.limit) - 1;
754 theirRequest.query["page[offset]"] = lastPage;
764 pageLinks.last = url.format(theirRequest);
77
784 if ((pageData.offset + pageData.limit) < handlerTotal) {
794 var nextPageOffset = pageData.offset + pageData.limit;
804 theirRequest.query["page[offset]"] = nextPageOffset;
814 pageLinks.next = url.format(theirRequest);
82 }
83 }
84
855 return pageLinks;
86};
87

/home/pmcnr/work/jsonapi-server/lib/postProcess.js

96%
54
52
2
LineHitsSource
1/* @flow weak */
21"use strict";
31var postProcess = module.exports = { };
4
51var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/..");
61var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js");
71var rerouter = require("/home/pmcnr/work/jsonapi-server/lib/./rerouter.js");
81var async = require("async");
91postProcess._applySort = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/sort.js").action;
101postProcess._applyFilter = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/filter.js").action;
111postProcess._applyIncludes = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/include.js").action;
121postProcess._applyFields = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/fields.js").action;
13
141postProcess.handle = function(request, response, callback) {
1589 async.waterfall([
16 function(next) {
1789 return postProcess._applySort(request, response, next);
18 },
19 function(next) {
2089 return postProcess._applyFilter(request, response, next);
21 },
22 function(next) {
2389 return postProcess._applyIncludes(request, response, next);
24 },
25 function(next) {
2689 return postProcess._applyFields(request, response, next);
27 }
28 ], function(err) {
2989 return callback(err);
30 });
31};
32
331postProcess._fetchRelatedResources = function(request, mainResource, callback) {
34
35 // Fetch the other objects
365 var dataItems = mainResource[request.params.relation];
37
386 if (!dataItems) return callback(null, [ null ]);
39
408 if (!(dataItems instanceof Array)) dataItems = [ dataItems ];
41
424 var resourcesToFetch = dataItems.reduce(function(map, dataItem) {
434 map[dataItem.type] = map[dataItem.type] || [ ];
444 map[dataItem.type].push(dataItem.id);
454 return map;
46 }, { });
47
484 resourcesToFetch = Object.keys(resourcesToFetch).map(function(type) {
494 var ids = resourcesToFetch[type];
504 var urlJoiner = "&filter[id]=";
514 ids = urlJoiner + ids.join(urlJoiner);
524 var uri = jsonApi._apiConfig.pathPrefix + type + "/?" + ids;
534 if (request.route.query) {
543 uri += "&" + request.route.query;
55 }
564 return uri;
57 });
58
594 async.map(resourcesToFetch, function(related, done) {
604 debug.include(related);
61
624 rerouter.route({
63 method: "GET",
64 uri: related,
65 originalRequest: request
66 }, function(err, json) {
674 if (err) {
680 debug.include("!!", JSON.stringify(err));
690 return done(err.errors);
70 }
71
724 var data = json.data;
734 if (!(data instanceof Array)) data = [ data ];
744 return done(null, data);
75 });
76 }, function(err, otherResources) {
774 if (err) return callback(err);
784 var relatedResources = [].concat.apply([], otherResources);
794 return callback(null, relatedResources);
80 });
81};
82
831postProcess.fetchForeignKeys = function(request, items, schema, callback) {
8487 if (!(items instanceof Array)) {
8521 items = [ items ];
86 }
8787 items.forEach(function(item) {
88266 for (var i in schema) {
892709 var settings = schema[i]._settings;
902709 if (settings && settings.__as) {
91209 item[i] = undefined;
92 }
93 }
94 });
9587 return callback();
96};
97

/home/pmcnr/work/jsonapi-server/lib/postProcessing/fields.js

95%
20
19
1
LineHitsSource
1/* @flow weak */
21"use strict";
31var fields = module.exports = { };
4
51var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../jsonApi.js");
6
71fields.action = function(request, response, callback) {
889 var resourceList = request.params.fields;
9172 if (!resourceList || !(resourceList instanceof Object)) return callback();
10
116 var allDataItems = response.included.concat(response.data);
12
136 for (var resource in resourceList) {
146 if (!jsonApi._resources[resource]) {
151 return callback({
16 status: "403",
17 code: "EFORBIDDEN",
18 title: "Invalid field resource",
19 detail: resource + " is not a valid resource "
20 });
21 }
22
235 var field = ("" + resourceList[resource]).split(",");
24
255 for (var i = 0; i < field.length; i++) {
266 var j = field[i];
276 if (!jsonApi._resources[resource].attributes[j]) {
280 return callback({
29 status: "403",
30 code: "EFORBIDDEN",
31 title: "Invalid field selection",
32 detail: resource + " do not have property " + j
33 });
34 }
35 }
36 }
37
385 allDataItems.forEach(function(dataItem) {
3911 Object.keys(dataItem.attributes).forEach(function(attribute) {
4049 if (field.indexOf(attribute) === -1) {
4134 delete dataItem.attributes[attribute];
42 }
43 });
44 });
45
465 return callback();
47};
48

/home/pmcnr/work/jsonapi-server/lib/postProcessing/filter.js

98%
59
58
1
LineHitsSource
1/* @flow weak */
21"use strict";
31var filter = module.exports = { };
4
51var _ = {
6 assign: require("lodash.assign"),
7 isEqual: require("lodash.isequal")
8};
91var debug = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../debugging.js");
10
111filter.action = function(request, response, callback) {
1289 var filters = request.processedFilter;
13137 if (!filters) return callback();
14
1541 if (response.data instanceof Array) {
1640 for (var j = 0; j < response.data.length; j++) {
17157 if (!filter._filterKeepObject(response.data[j], filters)) {
1888 debug.filter("removed", filters, JSON.stringify(response.data[j].attributes));
1988 response.data.splice(j, 1);
2088 j--;
21 }
22 }
231 } else if (response.data instanceof Object) {
241 if (!filter._filterKeepObject(response.data, filters)) {
251 debug.filter("removed", filters, JSON.stringify(response.data.attributes));
261 response.data = null;
27 }
28 }
29
3041 return callback();
31};
32
331filter._filterMatches = function(filterElement, attributeValue) {
34203 if (!filterElement.operator) {
35171 return _.isEqual(attributeValue, filterElement.value);
36 }
3732 var filterFunction = {
38 ">": function filterGreaterThan(attrValue, filterValue) {
398 return attrValue > filterValue;
40 },
41 "<": function filterLessThan(attrValue, filterValue) {
428 return attrValue < filterValue;
43 },
44 "~": function filterCaseInsensitiveEqual(attrValue, filterValue) {
458 return attrValue.toLowerCase() === filterValue.toLowerCase();
46 },
47 ":": function filterCaseInsensitiveContains(attrValue, filterValue) {
488 return attrValue.toLowerCase().indexOf(filterValue.toLowerCase()) !== -1;
49 }
50 }[filterElement.operator];
5132 var result = filterFunction(attributeValue, filterElement.value);
5232 return result;
53};
54
551filter._filterKeepObject = function(someObject, filters) {
56158 for (var filterName in filters) {
57160 var whitelist = filters[filterName];
58
59160 if (someObject.attributes.hasOwnProperty(filterName) || (filterName === "id")) {
60127 var attributeValue = someObject.attributes[filterName];
61201 if (filterName === "id") attributeValue = someObject.id;
62127 var attributeMatches = filter._attributesMatchesOR(attributeValue, whitelist);
63199 if (!attributeMatches) return false;
6433 } else if (someObject.relationships.hasOwnProperty(filterName)) {
6533 var relationships = someObject.relationships[filterName];
6633 var relationshipMatches = filter._relationshipMatchesOR(relationships, whitelist);
6750 if (!relationshipMatches) return false;
68 } else {
690 return false;
70 }
71 }
7269 return true;
73};
74
751filter._attributesMatchesOR = function(attributeValue, whitelist) {
76127 var matchOR = false;
77127 whitelist.forEach(function(filterElement) {
78203 if (filter._filterMatches(filterElement, attributeValue)) {
7955 matchOR = true;
80 }
81 });
82127 return matchOR;
83};
84
851filter._relationshipMatchesOR = function(relationships, whitelist) {
8633 var matchOR = false;
87
8833 var data = relationships.data;
8933 if (!data) return false;
90
9154 if (!(data instanceof Array)) data = [ data ];
9233 data = data.map(function(relation) {
9333 return relation.id;
94 });
95
9633 whitelist.forEach(function(filterElement) {
9759 if (data.indexOf(filterElement.value) !== -1) {
9816 matchOR = true;
99 }
100 });
10133 return matchOR;
102};
103

/home/pmcnr/work/jsonapi-server/lib/postProcessing/include.js

97%
110
107
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var includePP = module.exports = { };
4
51var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../jsonApi.js");
61var _ = {
7 uniq: require("lodash.uniq"),
8 uniqBy: require("lodash.uniqby")
9};
101var rerouter = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../rerouter.js");
111var async = require("async");
121var debug = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../debugging.js");
13
141includePP.action = function(request, response, callback) {
1589 var includes = request.params.include;
1689 var filters = request.params.filter || { };
17166 if (!includes) return callback();
1812 includes = ("" + includes).split(",");
19
2012 includePP._arrayToTree(request, includes, filters, function(attErr, includeTree) {
2114 if (attErr) return callback(attErr);
22
2312 var dataItems = response.data;
2416 if (!(dataItems instanceof Array)) dataItems = [ dataItems ];
2512 includeTree._dataItems = dataItems;
26
2712 includePP._fillIncludeTree(includeTree, request, function(fiErr) {
2812 if (fiErr) return callback(fiErr);
29
3012 includeTree._dataItems = [ ];
3112 response.included = includePP._getDataItemsFromTree(includeTree);
3212 response.included = _.uniqBy(response.included, function(someItem) {
3339 return someItem.type + "~~" + someItem.id;
34 });
35
3612 return callback();
37 });
38 });
39};
40
411includePP._arrayToTree = function(request, includes, filters, callback) {
4212 var tree = {
43 _dataItems: null,
44 _resourceConfig: request.resourceConfig
45 };
46
4712 var iterate = function(text, node, filter) {
4847 if (text.length === 0) return null;
4921 var parts = text.split(".");
5021 var first = parts.shift();
5121 var rest = parts.join(".");
52
5321 var resourceAttribute = node._resourceConfig.attributes[first];
5421 if (!resourceAttribute) {
551 return callback({
56 status: "403",
57 code: "EFORBIDDEN",
58 title: "Invalid inclusion",
59 detail: node._resourceConfig.resource + " do not have property " + first
60 });
61 }
6220 resourceAttribute = resourceAttribute._settings.__one || resourceAttribute._settings.__many;
6320 if (!resourceAttribute) {
640 return callback({
65 status: "403",
66 code: "EFORBIDDEN",
67 title: "Invalid inclusion",
68 detail: node._resourceConfig.resource + "." + first + " is not a relation and cannot be included"
69 });
70 }
71
7220 filter = filter[first] || { };
7320 if (filter instanceof Array) {
741 filter = filter.filter(function(i) {
752 return i instanceof Object;
76 }).pop();
77 }
78
7920 if (!node[first]) {
8020 node[first] = {
81 _dataItems: [ ],
82 _resourceConfig: jsonApi._resources[resourceAttribute],
83 _filter: [ ]
84 };
85
8620 if (!((filter instanceof Array) && (filter.length === 0))) {
8720 for (var i in filter) {
882 if (!(typeof filter[i] === "string" || (filter[i] instanceof Array))) continue;
892 node[first]._filter.push("filter[" + i + "]=" + filter[i]);
90 }
91 }
92 }
9320 iterate(rest, node[first], filter);
94 };
9512 includes.forEach(function(include) {
9614 iterate(include, tree, filters);
97 });
98
9912 return callback(null, tree);
100};
101
1021includePP._getDataItemsFromTree = function(tree) {
10332 var items = tree._dataItems;
10432 for (var i in tree) {
105104 if (i[0] !== "_") {
10620 items = items.concat(includePP._getDataItemsFromTree(tree[i]));
107 }
108 }
10932 return items;
110};
111
1121includePP._fillIncludeTree = function(includeTree, request, callback) {
113 /****
114 includeTree = {
115 _dataItems: [ ],
116 _filter: { },
117 _resourceConfig: { },
118 person: { includeTree },
119 booking: { includeTree }
120 };
121 ****/
12232 var includes = Object.keys(includeTree);
123
12432 var map = {
125 primary: { },
126 foreign: { }
127 };
12832 includeTree._dataItems.forEach(function(dataItem) {
12969 if (!dataItem) return [ ];
13069 return Object.keys(dataItem.relationships || { }).filter(function(keyName) {
131202 return (keyName[0] !== "_") && (includes.indexOf(keyName) !== -1);
132 }).forEach(function(relation) {
13347 var someRelation = dataItem.relationships[relation];
134
13547 if (someRelation.meta.relation === "primary") {
13635 var relationItems = someRelation.data;
13736 if (!relationItems) return;
13860 if (!(relationItems instanceof Array)) relationItems = [ relationItems ];
13934 relationItems.forEach(function(relationItem) {
14034 var key = relationItem.type + "~~" + relation + "~~" + relation;
14134 map.primary[key] = map.primary[key] || [ ];
14234 map.primary[key].push(relationItem.id);
143 });
144 }
145
14646 if (someRelation.meta.relation === "foreign") {
14712 var key = someRelation.meta.as + "~~" + someRelation.meta.belongsTo + "~~" + relation;
14812 map.foreign[key] = map.foreign[key] || [ ];
14912 map.foreign[key].push(dataItem.id);
150 }
151 });
152 });
153
15432 var resourcesToFetch = [];
155
15632 Object.keys(map.primary).forEach(function(relation) {
15713 var ids = _.uniq(map.primary[relation]);
15813 var parts = relation.split("~~");
15913 var urlJoiner = "&filter[id]=";
16013 ids = urlJoiner + ids.join(urlJoiner);
16113 if (includeTree[parts[1]]._filter) {
16213 ids += "&" + includeTree[parts[1]]._filter.join("&");
163 }
16413 resourcesToFetch.push({
165 url: jsonApi._apiConfig.pathPrefix + parts[0] + "/?" + ids,
166 as: relation
167 });
168 });
169
17032 Object.keys(map.foreign).forEach(function(relation) {
1716 var ids = _.uniq(map.foreign[relation]);
1726 var parts = relation.split("~~");
1736 var urlJoiner = "&filter[" + parts[0] + "]=";
1746 ids = urlJoiner + ids.join(urlJoiner);
1756 if (includeTree[parts[2]]._filter) {
1766 ids += "&" + includeTree[parts[2]]._filter.join("&");
177 }
1786 resourcesToFetch.push({
179 url: jsonApi._apiConfig.pathPrefix + parts[1] + "/?" + ids,
180 as: relation
181 });
182 });
183
18432 async.map(resourcesToFetch, function(related, done) {
18519 var parts = related.as.split("~~");
18619 debug.include(related);
187
18819 rerouter.route({
189 method: "GET",
190 uri: related.url,
191 originalRequest: request
192 }, function(err, json) {
19319 if (err) {
1940 debug.include("!!", JSON.stringify(err));
1950 return done(err.errors);
196 }
197
19819 var data = json.data;
19919 if (!data) return done();
20019 if (!(data instanceof Array)) data = [ data ];
20119 includeTree[parts[2]]._dataItems = includeTree[parts[2]]._dataItems.concat(data);
20219 return done();
203 });
204 }, function(err) {
20532 if (err) return callback(err);
206
20732 async.map(includes, function(include, done) {
208188 if (include[0] === "_") return done();
20920 includePP._fillIncludeTree(includeTree[include], request, done);
210 }, callback);
211 });
212};
213

/home/pmcnr/work/jsonapi-server/lib/postProcessing/sort.js

78%
19
15
4
LineHitsSource
1/* @flow weak */
21"use strict";
31var sort = module.exports = { };
4
51sort.action = function(request, response, callback) {
689 var attribute = request.params.sort;
789 var ascending = 1;
8171 if (!attribute) return callback();
97 attribute = ("" + attribute);
107 if (attribute[0] === "-") {
111 ascending = -1;
121 attribute = attribute.substring(1, attribute.length);
13 }
14
157 if (!request.resourceConfig.attributes[attribute]) {
160 return callback({
17 status: "403",
18 code: "EFORBIDDEN",
19 title: "Invalid sort",
20 detail: request.resourceConfig.resource + " do not have property " + attribute
21 });
22 }
23
247 response.data = response.data.sort(function(a, b) {
257 if (typeof a.attributes[attribute] === "string") {
267 return a.attributes[attribute].localeCompare(b.attributes[attribute]) * ascending;
270 } else if (typeof a.attributes[attribute] === "number") {
280 return (a.attributes[attribute] - b.attributes[attribute]) * ascending;
29 } else {
300 return 0;
31 }
32 });
33
347 return callback();
35};
36

/home/pmcnr/work/jsonapi-server/lib/rerouter.js

95%
41
39
2
LineHitsSource
1/* @flow weak */
21"use strict";
31var rerouter = module.exports = { };
4
51var router = require("/home/pmcnr/work/jsonapi-server/lib/./router.js");
61var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/./jsonApi.js");
71var url = require("qs");
81var _ = {
9 omit: require("lodash.omit")
10};
11
12
131rerouter.route = function(newRequest, callback) {
1423 var validRoutes = router._routes[newRequest.method.toLowerCase()];
15
1623 var path = rerouter._generateSanePath(newRequest);
1723 var route = rerouter._pickFirstMatchingRoute(validRoutes, path);
18
1923 var req = {
20 url: newRequest.uri,
21 headers: newRequest.originalRequest.headers,
22 cookies: newRequest.originalRequest.cookies,
23 params: url.parse(newRequest.uri.split("?").pop())
24 };
2523 rerouter._extendUrlParamsOntoReq(route, path, req);
26
2723 var res = {
28 status: function(httpCode) {
2923 res.httpCode = httpCode;
3023 return res;
31 },
32 json: function(payload) {
3323 var err = null;
3423 if (res.httpCode >= 400) {
350 err = payload;
360 payload = undefined;
37 }
3823 return callback(err, payload);
39 }
40 };
4123 validRoutes[route](req, res, _.omit(newRequest.originalRequest, [ "params", "route" ]));
42};
43
441rerouter._generateSanePath = function(newRequest) {
4523 var path = newRequest.uri;
4623 if (path.match(/^https?\:\/\//)) {
4723 path = path.split("/").slice(3).join("/");
48 }
4923 if (jsonApi._apiConfig.base !== "/") {
5046 if (path[0] !== "/") path = "/" + path;
5123 path = path.split(jsonApi._apiConfig.base);
5223 path.shift();
5323 path = path.join(jsonApi._apiConfig.base);
54 }
5523 path = path.replace(/^\//, "").split("?")[0].replace(/\/$/, "");
5623 return path;
57};
58
591rerouter._pickFirstMatchingRoute = function(validRoutes, path) {
6023 return Object.keys(validRoutes).filter(function(someRoute) {
61138 someRoute = someRoute.replace(/(\:[a-z]+)/g, "[^/]*?");
62138 someRoute = new RegExp("^" + someRoute);
63138 return someRoute.test(path);
64 }).pop();
65};
66
671rerouter._extendUrlParamsOntoReq = function(route, path, req) {
6823 route.split("/").forEach(function(urlPart, i) {
6923 if (urlPart[0] !== ":") return;
7023 req.params[urlPart.substring(1)] = path.split("/")[i];
71 });
72};
73

/home/pmcnr/work/jsonapi-server/lib/responseHelper.js

96%
84
81
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var responseHelper = module.exports = { };
4
51var _ = {
6 assign: require("lodash.assign"),
7 pick: require("lodash.pick")
8};
91var async = require("async");
101var pagination = require("/home/pmcnr/work/jsonapi-server/lib/./pagination.js");
111var Joi = require("joi");
121var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js");
13
14
151responseHelper.setBaseUrl = function(baseUrl) {
161 responseHelper._baseUrl = baseUrl;
17};
181responseHelper.setMetadata = function(meta) {
191 responseHelper._metadata = meta;
20};
21
221responseHelper._enforceSchemaOnArray = function(items, schema, callback) {
2366 if (!(items instanceof Array)) {
240 items = [ items ];
25 }
2666 async.map(items, function(item, done) {
27245 return responseHelper._enforceSchemaOnObject(item, schema, done);
28 }, function(err, results) {
2966 if (err) return callback(err);
30
3166 results = results.filter(function(result) {
32245 return !!result;
33 });
3466 return callback(null, results);
35 });
36};
37
381responseHelper._enforceSchemaOnObject = function(item, schema, callback) {
39266 debug.validationOutput(JSON.stringify(item));
40266 Joi.validate(item, schema, function (err, sanitisedItem) {
41266 if (err) {
420 debug.validationError(err.message, JSON.stringify(item));
430 return callback(null, null);
44 }
45
46266 var dataItem = responseHelper._generateDataItem(sanitisedItem, schema);
47266 return callback(null, dataItem);
48 });
49};
50
511responseHelper._generateDataItem = function(item, schema) {
52
53266 var isSpecialProperty = function(value) {
544620 if (!(value instanceof Object)) return false;
556312 if (value._settings) return true;
562928 return false;
57 };
58266 var linkProperties = Object.keys(schema).filter(function(someProperty) {
592709 return isSpecialProperty(schema[someProperty]);
60 });
61266 var attributeProperties = Object.keys(schema).filter(function(someProperty) {
622975 if (someProperty === "id") return false;
632709 if (someProperty === "type") return false;
642443 if (someProperty === "meta") return false;
651911 return !isSpecialProperty(schema[someProperty]);
66 });
67
68266 var result = {
69 type: item.type,
70 id: item.id,
71 attributes: _.pick(item, attributeProperties),
72 links: responseHelper._generateLinks(item, schema, linkProperties),
73 relationships: responseHelper._generateRelationships(item, schema, linkProperties),
74 meta: item.meta
75 };
76
77266 return result;
78};
79
801responseHelper._generateLinks = function(item) {
81266 return {
82 self: responseHelper._baseUrl + item.type + "/" + item.id
83 };
84};
85
861responseHelper._generateRelationships = function(item, schema, linkProperties) {
87266 if (linkProperties.length === 0) return undefined;
88
89266 var links = { };
90
91266 linkProperties.forEach(function(linkProperty) {
92846 links[linkProperty] = responseHelper._generateLink(item, schema[linkProperty], linkProperty);
93 });
94
95266 return links;
96};
97
981responseHelper._generateLink = function(item, schemaProperty, linkProperty) {
99846 var link = {
100 meta: {
101 relation: "primary",
102 // type: schemaProperty._settings.__many || schemaProperty._settings.__one,
103 readOnly: false
104 },
105 links: {
106 self: responseHelper._baseUrl + item.type + "/" + item.id + "/relationships/" + linkProperty,
107 related: responseHelper._baseUrl + item.type + "/" + item.id + "/" + linkProperty
108 },
109 data: null
110 };
111
112846 if (schemaProperty._settings.__many) {
113 // $FlowFixMe: the data property can be either undefined (not present), null or [ ]
114621 link.data = [ ];
115621 var linkItems = item[linkProperty];
116621 if (linkItems) {
117423 if (!(linkItems instanceof Array)) linkItems = [ linkItems ];
118423 linkItems.forEach(function(linkItem) {
119353 link.data.push({
120 type: linkItem.type,
121 id: linkItem.id,
122 meta: linkItem.meta
123 });
124 });
125 }
126 }
127
128846 if (schemaProperty._settings.__one) {
129225 var linkItem = item[linkProperty];
130225 if (linkItem) {
131 // $FlowFixMe: the data property can be either undefined (not present), null or [ ]
132202 link.data = {
133 type: linkItem.type,
134 id: linkItem.id,
135 meta: linkItem.meta
136 };
137 }
138 }
139
140846 if (schemaProperty._settings.__as) {
141209 var relatedResource = schemaProperty._settings.__one || schemaProperty._settings.__many;
142 // get information about the linkage - list of ids and types
143 // /rest/bookings/relationships/?customer=26aa8a92-2845-4e40-999f-1fa006ec8c63
144209 link.links.self = responseHelper._baseUrl + relatedResource + "/relationships/?" + schemaProperty._settings.__as + "=" + item.id;
145 // get full details of all linked resources
146 // /rest/bookings/?filter[customer]=26aa8a92-2845-4e40-999f-1fa006ec8c63
147209 link.links.related = responseHelper._baseUrl + relatedResource + "/?filter[" + schemaProperty._settings.__as + "]=" + item.id;
148209 if (!item[linkProperty]) {
149 // $FlowFixMe: the data property can be either undefined (not present), null or [ ]
150209 link.data = undefined;
151 }
152209 link.meta = {
153 relation: "foreign",
154 belongsTo: relatedResource,
155 as: schemaProperty._settings.__as,
156 many: !!schemaProperty._settings.__many,
157 readOnly: true
158 };
159 }
160
161846 return link;
162};
163
1641responseHelper.generateError = function(request, err) {
16545 debug.errors(request.route.verb, request.route.combined, JSON.stringify(err));
16690 if (!(err instanceof Array)) err = [ err ];
167
16845 var errorResponse = {
169 jsonapi: {
170 version: "1.0"
171 },
172 meta: responseHelper._generateMeta(request),
173 links: {
174 self: responseHelper._baseUrl + request.route.path
175 },
176 errors: err.map(function(error) {
17745 return {
178 status: error.status,
179 code: error.code,
180 title: error.title,
181 detail: error.detail
182 };
183 })
184 };
185
18645 return errorResponse;
187};
188
1891responseHelper._generateResponse = function(request, resourceConfig, sanitisedData, handlerTotal) {
19093 return {
191 jsonapi: {
192 version: "1.0"
193 },
194 meta: responseHelper._generateMeta(request, handlerTotal),
195 links: _.assign({
196 self: responseHelper._baseUrl + request.route.path + (request.route.query ? ("?" + request.route.query) : "")
197 }, pagination.generatePageLinks(request, handlerTotal)),
198 data: sanitisedData
199 };
200};
201
2021responseHelper._generateMeta = function(request, handlerTotal) {
203139 var meta = _.assign({ }, responseHelper._metadata);
204
205139 if (handlerTotal) {
20666 meta.page = pagination.generateMetaSummary(request, handlerTotal);
207 }
208
209139 return meta;
210};
211

/home/pmcnr/work/jsonapi-server/lib/router.js

96%
89
86
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var router = module.exports = { };
4
51var _ = {
6 assign: require("lodash.assign"),
7 omit: require("lodash.omit")
8};
91var express = require("express");
101var app = express();
111var server;
121var bodyParser = require("body-parser");
131var cookieParser = require("cookie-parser");
141var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/./jsonApi.js");
151var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js");
161var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/./responseHelper.js");
171var url = require("url");
18
191app.use(function(req, res, next) {
20212 if (!req.headers["content-type"] && !req.headers.accept) return next();
21
2222 if (req.headers["content-type"]) {
23 // 415 Unsupported Media Type
2421 if (req.headers["content-type"].match(/^application\/vnd\.api\+json;.+$/)) {
251 return res.status(415).end();
26 }
27
28 // Convert "application/vnd.api+json" content type to "application/json".
29 // This enables the express body parser to correctly parse the JSON payload.
3020 if (req.headers["content-type"].match(/^application\/vnd\.api\+json$/)) {
3120 req.headers["content-type"] = "application/json";
32 }
33 }
34
3521 if (req.headers.accept) {
36 // 406 Not Acceptable
371 var matchingTypes = req.headers.accept.split(/, ?/);
381 matchingTypes = matchingTypes.filter(function(mediaType) {
39 // Accept application/*, */vnd.api+json, */* and the correct JSON:API type.
403 return mediaType.match(/^(\*|application)\/(\*|vnd\.api\+json)$/) || mediaType.match(/\*\/\*/);
41 });
42
431 if (matchingTypes.length === 0) {
441 return res.status(406).end();
45 }
46 }
47
4820 return next();
49});
50
511app.use(function(req, res, next) {
52115 res.set({
53 "Content-Type": "application/vnd.api+json",
54 "Access-Control-Allow-Origin": "*",
55 "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
56 "Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "",
57 "Cache-Control": "private, must-revalidate, max-age=0",
58 "Expires": "Thu, 01 Jan 1970 00:00:00"
59 });
60
61115 if (req.method === "OPTIONS") {
621 return res.status(204).end();
63 }
64
65114 return next();
66});
67
681app.use(bodyParser.json());
691app.use(bodyParser.urlencoded({ extended: true }));
701app.use(cookieParser());
711app.disable("x-powered-by");
721app.disable("etag");
73
741var requestId = 0;
751app.route("*").all(function(req, res, next) {
76114 debug.requestCounter(requestId++, req.method, req.url);
77114 if (requestId > 1000) requestId = 0;
78114 next();
79});
80
811router.listen = function(port) {
82
8320 if (!server) {
8419 if (jsonApi._apiConfig.protocol === "https") {
850 server = require("https").createServer(jsonApi._apiConfig.tls || {}, app);
86 } else {
8719 server = require("http").createServer(app);
88 }
8919 server.listen(port);
90 }
91};
92
931router.close = function() {
9419 server.close();
9519 server = null;
96};
97
981router._routes = { };
991router.bindRoute = function(config, callback) {
100240 var path = jsonApi._apiConfig.base + config.path;
101240 var verb = config.verb.toLowerCase();
102
103240 var routeHandler = function(req, res, extras) {
104135 var request = router._getParams(req);
105135 request = _.assign(request, extras);
106135 var resourceConfig = jsonApi._resources[request.params.type];
107135 request.resourceConfig = resourceConfig;
108135 router.authenticate(request, res, function() {
109133 return callback(request, resourceConfig, res);
110 });
111 };
112240 router._routes[verb] = router._routes[verb] || { };
113240 router._routes[verb][config.path] = routeHandler;
114240 app[verb](path, routeHandler);
115};
116
1171router.authenticate = function(request, res, callback) {
118135 if (!router._authFunction) return callback();
119
120135 router._authFunction(request, function(err) {
121268 if (!err) return callback();
122
1232 var errorWrapper = {
124 status: "401",
125 code: "UNAUTHORIZED",
126 title: "Authentication Failed",
127 detail: err || "You are not authorised to access this resource."
128 };
1292 var payload = responseHelper.generateError(request, errorWrapper);
1302 res.status(401).json(payload);
131 });
132};
133
1341router.authenticateWith = function(authFunction) {
1351 router._authFunction = authFunction;
136};
137
1381router.bind404 = function(callback) {
13920 app.use(function(req, res) {
1402 var request = router._getParams(req);
1412 return callback(request, res);
142 });
143};
144
1451router.bindErrorHandler = function(callback) {
14620 app.use(function(error, req, res, next) {
1470 var request = router._getParams(req);
1480 return callback(request, res, error, next);
149 });
150};
151
1521router._getParams = function(req) {
153137 var urlParts = req.url.split(jsonApi._apiConfig.base);
154137 urlParts.shift();
155137 urlParts = urlParts.join(jsonApi._apiConfig.base).split("?");
156
157137 var headersToRemove = [
158 "host", "connection", "accept-encoding", "accept-language", "content-length"
159 ];
160
161137 return {
162 params: _.assign(req.params, req.body, req.query),
163 headers: req.headers,
164 safeHeaders: _.omit(req.headers, headersToRemove),
165 cookies: req.cookies,
166 route: {
167 verb: req.method,
168 host: req.headers.host,
169 base: jsonApi._apiConfig.base,
170 path: urlParts.shift() || "",
171 query: urlParts.shift() || "",
172 combined: url.format({
173 protocol: jsonApi._apiConfig.protocol,
174 hostname: jsonApi._apiConfig.hostname,
175 port: jsonApi._apiConfig.port
176 }) + req.url
177 }
178 };
179};
180
1811router.sendResponse = function(res, payload, httpCode) {
182135 res.status(httpCode).json(payload);
183};
184

/home/pmcnr/work/jsonapi-server/lib/routes/_foreignKeySearch.js

97%
37
36
1
LineHitsSource
1/* @flow weak */
21"use strict";
31var foreignKeySearchRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
91var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
10
11
121foreignKeySearchRoute.register = function() {
1320 router.bindRoute({
14 verb: "get",
15 path: ":type/relationships/?"
16 }, function(request, resourceConfig, res) {
173 var foreignKey;
183 var searchResults;
193 var response;
20
213 async.waterfall([
22 function(callback) {
233 helper.verifyRequest(request, resourceConfig, res, "search", callback);
24 },
25 function(callback) {
262 foreignKey = Object.keys(request.params).filter(function(param) {
274 return ["include", "type", "sort", "filter", "fields", "requestId"].indexOf(param) === -1;
28 }).pop();
292 request.params.relationships = { };
302 request.params.relationships[foreignKey] = request.params[foreignKey];
312 delete request.params[foreignKey];
322 callback();
33 },
34 function(callback) {
352 var foreignKeySchema = resourceConfig.attributes[foreignKey];
362 if (!foreignKeySchema || !foreignKeySchema._settings) {
371 return callback({
38 status: "403",
39 code: "EFORBIDDEN",
40 title: "Invalid foreign key lookup",
41 detail: "Relation [" + foreignKey + "] does not exist within " + request.params.type
42 });
43 }
441 if (!(foreignKeySchema._settings.__one || foreignKeySchema._settings.__many)) {
450 return callback({
46 status: "403",
47 code: "EFORBIDDEN",
48 title: "Invalid foreign key lookup",
49 detail: "Attribute [" + foreignKey + "] does not represent a relation within " + request.params.type
50 });
51 }
521 callback();
53 },
54 function(callback) {
551 resourceConfig.handlers.search(request, callback);
56 },
57 function(results, pageData, callback) {
581 searchResults = results.map(function(result) {
594 return {
60 id: result.id,
61 type: result.type
62 };
63 });
641 if (resourceConfig.attributes[foreignKey]) {
651 searchResults = searchResults[0] || null;
66 }
671 callback();
68 },
69 function(callback) {
701 response = responseHelper._generateResponse(request, resourceConfig, searchResults);
711 response.included = [ ];
721 postProcess.handle(request, response, callback);
73 }
74 ], function(err) {
753 if (err) return helper.handleError(request, res, err);
761 return router.sendResponse(res, response, 200);
77 });
78 });
79};
80

/home/pmcnr/work/jsonapi-server/lib/routes/_swagger.js

72%
11
8
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var swagger = module.exports = { };
4
51var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
61var swaggerGenerator = require("/home/pmcnr/work/jsonapi-server/lib/routes/../swagger");
71var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/routes/../../");
8
9
101swagger.register = function() {
1120 if (!jsonApi._apiConfig.swagger) return;
12
1320 router.bindRoute({
14 verb: "get",
15 path: "swagger.json"
16 }, function(request, resourceConfig, res) {
170 if (!swagger._cache) {
180 swagger._cache = swaggerGenerator.generateDocumentation();
19 }
20
210 return res.json(swagger._cache);
22 });
23};
24

/home/pmcnr/work/jsonapi-server/lib/routes/addRelation.js

100%
32
32
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var addRelationRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
91var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
10
11
121addRelationRoute.register = function() {
1320 router.bindRoute({
14 verb: "post",
15 path: ":type/:id/relationships/:relation"
16 }, function(request, resourceConfig, res) {
174 var newResource;
184 var theirResource;
194 var response;
20
214 async.waterfall([
22 function(callback) {
234 helper.verifyRequest(request, resourceConfig, res, "update", callback);
24 },
25 function(callback) {
263 helper.verifyRequest(request, resourceConfig, res, "find", callback);
27 },
28 function(callback) {
293 helper.checkForBody(request, callback);
30 },
31 function(callback) {
323 resourceConfig.handlers.find(request, callback);
33 },
34 function(ourResource, callback) {
352 theirResource = JSON.parse(JSON.stringify(ourResource));
36
372 var theirs = request.params.data;
382 theirResource[request.params.relation] = theirResource[request.params.relation] || [ ];
392 theirResource[request.params.relation].push(theirs);
402 helper.validate(theirResource, resourceConfig.onCreate, callback);
41 },
42 function(callback) {
431 resourceConfig.handlers.update(request, theirResource, callback);
44 },
45 function(result, callback) {
461 resourceConfig.handlers.find(request, callback);
47 },
48 function(result, callback) {
491 newResource = result;
501 postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback);
51 },
52 function(callback) {
531 responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback);
54 },
55 function(sanitisedData, callback) {
561 sanitisedData = sanitisedData.relationships[request.params.relation].data;
571 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
581 postProcess.handle(request, response, callback);
59 }
60 ], function(err) {
615 if (err) return helper.handleError(request, res, err);
621 router.sendResponse(res, response, 201);
63 });
64 });
65};
66

/home/pmcnr/work/jsonapi-server/lib/routes/create.js

100%
36
36
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var createRoute = module.exports = { };
4
51var async = require("async");
61var _ = {
7 assign: require("lodash.assign")
8};
91var uuid = require("node-uuid");
101var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
111var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
121var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
131var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
14
15
161createRoute.register = function() {
1720 router.bindRoute({
18 verb: "post",
19 path: ":type"
20 }, function(request, resourceConfig, res) {
214 var theirResource;
224 var newResource;
234 var response;
24
254 async.waterfall([
26 function(callback) {
274 helper.verifyRequest(request, resourceConfig, res, "create", callback);
28 },
29 function(callback) {
303 helper.verifyRequest(request, resourceConfig, res, "find", callback);
31 },
32 function(callback) {
333 helper.checkForBody(request, callback);
34 },
35 function(callback) {
362 var theirs = request.params.data;
372 theirResource = _.assign({
38 id: uuid.v4(),
39 type: request.params.type
40 }, theirs.attributes, { meta: theirs.meta });
412 for (var i in theirs.relationships) {
421 theirResource[i] = theirs.relationships[i].data;
43 }
442 helper.validate(theirResource, resourceConfig.onCreate, callback);
45 },
46 function(callback) {
471 resourceConfig.handlers.create(request, theirResource, callback);
48 },
49 function(result, callback) {
501 newResource = result;
511 request.params.id = newResource.id;
521 resourceConfig.handlers.find(request, callback);
53 },
54 function(result, callback) {
551 newResource = result;
561 postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback);
57 },
58 function(callback) {
591 responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback);
60 },
61 function(sanitisedData, callback) {
621 request.route.path += "/" + newResource.id;
631 res.set({
64 "Location": request.route.combined + "/" + newResource.id
65 });
661 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
671 postProcess.handle(request, response, callback);
68 }
69 ], function(err) {
705 if (err) return helper.handleError(request, res, err);
711 return router.sendResponse(res, response, 201);
72 });
73 });
74};
75

/home/pmcnr/work/jsonapi-server/lib/routes/delete.js

100%
14
14
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var deleteRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
9
10
111deleteRoute.register = function() {
1220 router.bindRoute({
13 verb: "delete",
14 path: ":type/:id"
15 }, function(request, resourceConfig, res) {
164 async.waterfall([
17 function(callback) {
184 helper.verifyRequest(request, resourceConfig, res, "delete", callback);
19 },
20 function(callback) {
212 resourceConfig.handlers.delete(request, callback);
22 }
23 ], function(err) {
243 if (err) return helper.handleError(request, res, err);
25
261 var response = {
27 meta: responseHelper._generateMeta(request)
28 };
291 router.sendResponse(res, response, 200);
30 });
31 });
32};
33

/home/pmcnr/work/jsonapi-server/lib/routes/find.js

100%
24
24
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var findRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var filter = require("/home/pmcnr/work/jsonapi-server/lib/routes/../filter.js");
91var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
101var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
11
12
131findRoute.register = function() {
1420 router.bindRoute({
15 verb: "get",
16 path: ":type/:id"
17 }, function(request, resourceConfig, res) {
1813 var resource;
1913 var response;
20
2113 async.waterfall([
22 function(callback) {
2313 helper.verifyRequest(request, resourceConfig, res, "find", callback);
24 },
25 function parseAndValidateFilter(callback) {
2612 return callback(filter.parseAndValidate(request));
27 },
28 function(callback) {
2912 resourceConfig.handlers.find(request, callback);
30 },
31 function(result, callback) {
3210 resource = result;
3310 postProcess.fetchForeignKeys(request, resource, resourceConfig.attributes, callback);
34 },
35 function(callback) {
3610 responseHelper._enforceSchemaOnObject(resource, resourceConfig.attributes, callback);
37 },
38 function(sanitisedData, callback) {
3910 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
4010 response.included = [ ];
4110 postProcess.handle(request, response, callback);
42 }
43 ], function(err) {
4414 if (err) return helper.handleError(request, res, err);
4510 return router.sendResponse(res, response, 200);
46 });
47 });
48};
49

/home/pmcnr/work/jsonapi-server/lib/routes/helper.js

100%
30
30
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var helper = module.exports = { };
4
51var Joi = require("joi");
61var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var debug = require("/home/pmcnr/work/jsonapi-server/lib/routes/../debugging.js");
91var _ = {
10 assign: require("lodash.assign")
11};
12
13
141helper.validate = function(someObject, someDefinition, callback) {
1588 debug.validationInput(JSON.stringify(someObject));
1688 Joi.validate(someObject, someDefinition, { abortEarly: false }, function (err, sanitisedObject) {
1788 if (err) {
188 return callback({
19 status: "403",
20 code: "EFORBIDDEN",
21 title: "Param validation failed",
22 detail: err.details
23 });
24 }
2580 _.assign(someObject, sanitisedObject);
2680 callback();
27 });
28};
29
301helper.checkForBody = function(request, callback) {
3121 if (!request.params.data) {
321 return callback({
33 status: "403",
34 code: "EFORBIDDEN",
35 title: "Request validation failed",
36 detail: "Missing \"data\" - have you sent the right http headers?"
37 });
38 }
3920 callback();
40};
41
421helper.handleError = function(request, res, err) {
4343 var errorResponse = responseHelper.generateError(request, err);
4443 var httpCode = errorResponse.errors[0].status || 500;
4543 return router.sendResponse(res, errorResponse, httpCode);
46};
47
481helper.verifyRequest = function(request, resourceConfig, res, handlerRequest, callback) {
49154 if (!resourceConfig) {
508 return helper.handleError(request, res, {
51 status: "404",
52 code: "ENOTFOUND",
53 title: "Resource not found",
54 detail: "The requested resource '" + request.params.type + "' does not exist"
55 });
56 }
57
58146 if (!resourceConfig.handlers.ready) {
591 return helper.handleError(request, res, {
60 status: "503",
61 code: "EUNAVAILABLE",
62 title: "Resource temporarily unavailable",
63 detail: "The requested resource '" + request.params.type + "' is temporarily unavailable"
64 });
65 }
66
67145 if (!resourceConfig.handlers[handlerRequest]) {
681 return helper.handleError(request, res, {
69 status: "403",
70 code: "EFORBIDDEN",
71 title: "Resource not supported",
72 detail: "The requested resource '" + request.params.type + "' does not support '" + handlerRequest + "'"
73 });
74 }
75
76144 return callback();
77};
78

/home/pmcnr/work/jsonapi-server/lib/routes/index.js

100%
11
11
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var routes = module.exports = { };
4
51var fs = require("fs");
61var path = require("path");
7
8
91routes.handlers = { };
101fs.readdirSync(__dirname).filter(function(filename) {
1116 return /\.js$/.test(filename) && (filename !== "index.js") && (filename !== "helper.js");
12}).sort().forEach(function(filename) {
1314 routes.handlers[filename] = require(path.join(__dirname, filename));
14});
15
161routes.register = function() {
1720 for (var i in routes.handlers) {
18280 routes.handlers[i].register();
19 }
20};
21

/home/pmcnr/work/jsonapi-server/lib/routes/related.js

100%
35
35
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var relatedRoute = module.exports = { };
4
51var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/routes/../jsonApi.js");
61var async = require("async");
71var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
81var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
91var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
101var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
11
12
131relatedRoute.register = function() {
1420 router.bindRoute({
15 verb: "get",
16 path: ":type/:id/:relation"
17 }, function(request, resourceConfig, res) {
188 var relation;
198 var mainResource;
208 var relatedResources;
218 var response;
22
238 async.waterfall([
24 function(callback) {
258 helper.verifyRequest(request, resourceConfig, res, "find", callback);
26 },
27 function(callback) {
288 relation = resourceConfig.attributes[request.params.relation];
298 if (!relation || !relation._settings || !(relation._settings.__one || relation._settings.__many)) {
301 return callback({
31 status: "404",
32 code: "ENOTFOUND",
33 title: "Resource not found",
34 detail: "The requested relation does not exist within the requested type"
35 });
36 }
377 if (relation._settings.__as) {
381 return callback({
39 status: "404",
40 code: "EFOREIGN",
41 title: "Relation is Foreign",
42 detail: "The requested relation is a foreign relation and cannot be accessed in this manner."
43 });
44 }
456 callback();
46 },
47 function(callback) {
486 resourceConfig.handlers.find(request, callback);
49 },
50 function(result, callback) {
515 mainResource = result;
525 postProcess._fetchRelatedResources(request, mainResource, callback);
53 },
54 function(newResources, callback) {
555 relatedResources = newResources;
565 if (relation._settings.__one) {
575 relatedResources = relatedResources[0];
58 }
595 request.resourceConfig = jsonApi._resources[relation._settings.__one || relation._settings.__many];
605 response = responseHelper._generateResponse(request, resourceConfig, relatedResources);
615 if (relatedResources !== null) {
624 response.included = [ ];
63 }
645 postProcess.handle(request, response, callback);
65 }
66 ], function(err) {
6711 if (err) return helper.handleError(request, res, err);
685 return router.sendResponse(res, response, 200);
69 });
70 });
71};
72

/home/pmcnr/work/jsonapi-server/lib/routes/relationships.js

100%
26
26
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var relationshipsRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
91var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
10
11
121relationshipsRoute.register = function() {
1320 router.bindRoute({
14 verb: "get",
15 path: ":type/:id/relationships/:relation"
16 }, function(request, resourceConfig, res) {
176 var resource;
186 var response;
19
206 async.waterfall([
21 function(callback) {
226 helper.verifyRequest(request, resourceConfig, res, "find", callback);
23 },
24 function(callback) {
256 var relation = resourceConfig.attributes[request.params.relation];
266 if (!relation || !(relation._settings.__one || relation._settings.__many)) {
271 return callback({
28 status: "404",
29 code: "ENOTFOUND",
30 title: "Resource not found",
31 detail: "The requested relation does not exist within the requested type"
32 });
33 }
345 callback();
35 },
36 function(callback) {
375 resourceConfig.handlers.find(request, callback);
38 },
39 function(result, callback) {
404 resource = result;
414 postProcess.fetchForeignKeys(request, resource, resourceConfig.attributes, callback);
42 },
43 function(callback) {
444 responseHelper._enforceSchemaOnObject(resource, resourceConfig.attributes, callback);
45 },
46 function(sanitisedData, callback) {
474 sanitisedData = sanitisedData.relationships[request.params.relation].data;
484 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
494 callback();
50 }
51 ], function(err) {
528 if (err) return helper.handleError(request, res, err);
534 return router.sendResponse(res, response, 200);
54 });
55 });
56};
57

/home/pmcnr/work/jsonapi-server/lib/routes/removeRelation.js

100%
42
42
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var removeRelationRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
91var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
10
11
121removeRelationRoute.register = function() {
1320 router.bindRoute({
14 verb: "delete",
15 path: ":type/:id/relationships/:relation"
16 }, function(request, resourceConfig, res) {
175 var newResource;
185 var theirResource;
195 var response;
20
215 async.waterfall([
22 function(callback) {
235 helper.verifyRequest(request, resourceConfig, res, "update", callback);
24 },
25 function(callback) {
264 helper.verifyRequest(request, resourceConfig, res, "find", callback);
27 },
28 function(callback) {
294 helper.checkForBody(request, callback);
30 },
31 function(callback) {
324 resourceConfig.handlers.find(request, callback);
33 },
34 function(ourResource, callback) {
353 theirResource = ourResource;
36
373 var theirs = request.params.data;
383 theirResource[request.params.relation] = theirResource[request.params.relation] || [ ];
396 if (!(theirs instanceof Array)) theirs = [ theirs ];
40
413 var keys = theirResource[request.params.relation].map(function(j) {
426 return j.id;
43 });
44
453 for (var i = 0; i < theirs.length; i++) {
463 if (theirs[i].type !== request.params.relation) {
471 return callback({
48 status: "403",
49 code: "EFORBIDDEN",
50 title: "Invalid Request",
51 detail: "Invalid type " + theirs[i].type
52 });
53 }
542 var someId = theirs[i].id;
552 var indexOfTheirs = keys.indexOf(someId);
562 if (indexOfTheirs === -1) {
571 return callback({
58 status: "403",
59 code: "EFORBIDDEN",
60 title: "Invalid Request",
61 detail: "Unknown id " + someId
62 });
63 }
641 theirResource[request.params.relation].splice(indexOfTheirs, 1);
65 }
66
671 helper.validate(theirResource, resourceConfig.onCreate, callback);
68 },
69 function(callback) {
701 resourceConfig.handlers.update(request, theirResource, callback);
71 },
72 function(result, callback) {
731 resourceConfig.handlers.find(request, callback);
74 },
75 function(result, callback) {
761 newResource = result;
771 postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback);
78 },
79 function(callback) {
801 responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback);
81 },
82 function(sanitisedData, callback) {
831 sanitisedData = sanitisedData.relationships[request.params.relation].data;
841 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
851 postProcess.handle(request, response, callback);
86 }
87 ], function(err) {
887 if (err) return helper.handleError(request, res, err);
891 router.sendResponse(res, response, 200);
90 });
91 });
92};
93

/home/pmcnr/work/jsonapi-server/lib/routes/search.js

100%
31
31
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var searchRoute = module.exports = { };
4
51var async = require("async");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
81var filter = require("/home/pmcnr/work/jsonapi-server/lib/routes/../filter.js");
91var pagination = require("/home/pmcnr/work/jsonapi-server/lib/routes/../pagination.js");
101var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
111var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
12
13
141searchRoute.register = function() {
1520 router.bindRoute({
16 verb: "get",
17 path: ":type"
18 }, function(request, resourceConfig, res) {
1973 var searchResults;
2073 var response;
2173 var paginationInfo;
22
2373 async.waterfall([
24 function(callback) {
2573 helper.verifyRequest(request, resourceConfig, res, "search", callback);
26 },
27 function(callback) {
2872 helper.validate(request.params, resourceConfig.searchParams, callback);
29 },
30 function parseAndValidateFilter(callback) {
3171 return callback(filter.parseAndValidate(request));
32 },
33 function validatePaginationParams(callback) {
3466 pagination.validatePaginationParams(request);
3566 return callback();
36 },
37 function(callback) {
3866 resourceConfig.handlers.search(request, callback);
39 },
40 function enforcePagination(results, pageInfo, callback) {
4166 searchResults = pagination.enforcePagination(request, results);
4266 paginationInfo = pageInfo;
4366 return callback();
44 },
45 function(callback) {
4666 postProcess.fetchForeignKeys(request, searchResults, resourceConfig.attributes, callback);
47 },
48 function(callback) {
4966 responseHelper._enforceSchemaOnArray(searchResults, resourceConfig.attributes, callback);
50 },
51 function(sanitisedData, callback) {
5266 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData, paginationInfo);
5366 response.included = [ ];
5466 postProcess.handle(request, response, callback);
55 }
56 ], function(err) {
5780 if (err) return helper.handleError(request, res, err);
5864 return router.sendResponse(res, response, 200);
59 });
60 });
61};
62

/home/pmcnr/work/jsonapi-server/lib/routes/update.js

100%
33
33
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var updateRoute = module.exports = { };
4
51var async = require("async");
61var _ = {
7 assign: require("lodash.assign"),
8 pick: require("lodash.pick")
9};
101var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
111var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
121var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
131var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
14
15
161updateRoute.register = function() {
1720 router.bindRoute({
18 verb: "patch",
19 path: ":type/:id"
20 }, function(request, resourceConfig, res) {
219 var theirResource;
229 var newResource;
239 var response;
24
259 async.waterfall([
26 function(callback) {
279 helper.verifyRequest(request, resourceConfig, res, "update", callback);
28 },
29 function(callback) {
308 helper.verifyRequest(request, resourceConfig, res, "find", callback);
31 },
32 function(callback) {
338 helper.checkForBody(request, callback);
34 },
35 function(callback) {
368 var theirs = request.params.data;
378 theirResource = _.assign({
38 id: request.params.id,
39 type: request.params.type
40 }, theirs.attributes, { meta: theirs.meta });
418 for (var i in theirs.relationships) {
425 theirResource[i] = theirs.relationships[i].data;
43 }
448 callback();
45 },
46 function(callback) {
478 var validationObject = _.pick(resourceConfig.onCreate, Object.keys(theirResource));
488 helper.validate(theirResource, validationObject, callback);
49 },
50 function(callback) {
514 resourceConfig.handlers.update(request, theirResource, callback);
52 },
53 function(result, callback) {
543 resourceConfig.handlers.find(request, callback);
55 },
56 function(result, callback) {
573 newResource = result;
583 postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback);
59 },
60 function(callback) {
613 responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback);
62 },
63 function(sanitisedData, callback) {
643 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
653 postProcess.handle(request, response, callback);
66 }
67 ], function(err) {
6813 if (err) return helper.handleError(request, res, err);
693 router.sendResponse(res, response, 200);
70 });
71 });
72};
73

/home/pmcnr/work/jsonapi-server/lib/routes/updateRelation.js

100%
31
31
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var updateRelationRoute = module.exports = { };
4
51var async = require("async");
61var _ = {
7 assign: require("lodash.assign")
8};
91var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
101var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
111var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js");
121var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js");
13
14
151updateRelationRoute.register = function() {
1620 router.bindRoute({
17 verb: "patch",
18 path: ":type/:id/relationships/:relation"
19 }, function(request, resourceConfig, res) {
204 var newResource;
214 var theirResource;
224 var response;
23
244 async.waterfall([
25 function(callback) {
264 helper.verifyRequest(request, resourceConfig, res, "update", callback);
27 },
28 function(callback) {
293 helper.verifyRequest(request, resourceConfig, res, "find", callback);
30 },
31 function(callback) {
323 helper.checkForBody(request, callback);
33 },
34 function(callback) {
353 var theirs = request.params.data;
363 theirResource = _.assign({
37 id: request.params.id,
38 type: request.params.type
39 });
403 theirResource[request.params.relation] = theirs;
413 helper.validate(theirResource, resourceConfig.onCreate, callback);
42 },
43 function(callback) {
442 resourceConfig.handlers.update(request, theirResource, callback);
45 },
46 function(result, callback) {
471 resourceConfig.handlers.find(request, callback);
48 },
49 function(result, callback) {
501 newResource = result;
511 postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback);
52 },
53 function(callback) {
541 responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback);
55 },
56 function(sanitisedData, callback) {
571 sanitisedData = sanitisedData.relationships[request.params.relation].data;
581 response = responseHelper._generateResponse(request, resourceConfig, sanitisedData);
591 postProcess.handle(request, response, callback);
60 }
61 ], function(err) {
625 if (err) return helper.handleError(request, res, err);
631 router.sendResponse(res, response, 200);
64 });
65 });
66};
67

/home/pmcnr/work/jsonapi-server/lib/routes/z404.js

100%
7
7
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var fourOhFour = module.exports = { };
4
51var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
61var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
7
8
91fourOhFour.register = function() {
1020 router.bind404(function(request, res) {
112 return helper.handleError(request, res, {
12 status: "404",
13 code: "EINVALID",
14 title: "Invalid Route",
15 detail: "This is not the API you are looking for?"
16 });
17 });
18};
19

/home/pmcnr/work/jsonapi-server/lib/routes/zerrorHandler.js

70%
10
7
3
LineHitsSource
1/* @flow weak */
21"use strict";
31var errorHandler = module.exports = { };
4
51var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/routes/../jsonApi.js");
61var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js");
71var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js");
8
9
101errorHandler.register = function() {
1120 router.bindErrorHandler(function(request, res, error) {
12
130 if (jsonApi._errHandler) {
140 jsonApi._errHandler(request, error);
15 }
16
170 return helper.handleError(request, res, {
18 status: "500",
19 code: "EUNKNOWN",
20 title: "An unknown error has occured. Sorry?",
21 detail: "??"
22 });
23 });
24};
25

/home/pmcnr/work/jsonapi-server/lib/swagger/index.js

100%
13
13
0
LineHitsSource
1/* @flow weak */
21"use strict";
31var swagger = module.exports = { };
41var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/swagger/../../");
51var swaggerPaths = require("/home/pmcnr/work/jsonapi-server/lib/swagger/./paths.js");
61var swaggerResources = require("/home/pmcnr/work/jsonapi-server/lib/swagger/./resources.js");
7
81swagger.generateDocumentation = function() {
91 var swaggerDoc = swagger._getSwaggerBase();
101 swaggerDoc.paths = swaggerPaths.getPathDefinitions(jsonApi);
111 swaggerDoc.definitions = swaggerResources.getResourceDefinitions(jsonApi);
121 return swaggerDoc;
13};
14
151swagger._getSwaggerBase = function() {
161 var swaggerConfig = jsonApi._apiConfig.swagger || { };
171 return {
18 swagger: "2.0",
19 info: {
20 title: swaggerConfig.title,
21 version: swaggerConfig.version,
22 description: swaggerConfig.description,
23 contact: {
24 name: (swaggerConfig.contact || { }).name,
25 email: (swaggerConfig.contact || { }).email,
26 url: (swaggerConfig.contact || { }).url
27 },
28 license: {
29 name: (swaggerConfig.license || { }).name,
30 url: (swaggerConfig.license || { }).url
31 }
32 },
33 host: jsonApi._apiConfig.host,
34 basePath: jsonApi._apiConfig.base.substring(0, jsonApi._apiConfig.base.length - 1),
35 schemes: [ jsonApi._apiConfig.protocol ],
36 consumes: [
37 "application/vnd.api+json"
38 ],
39 produces: [
40 "application/vnd.api+json"
41 ],
42 parameters: {
43 sort: {
44 name: "sort",
45 in: "query",
46 description: "Sort resources as per the JSON:API specification",
47 required: false,
48 type: "string"
49 },
50 include: {
51 name: "include",
52 in: "query",
53 description: "Fetch additional resources as per the JSON:API specification",
54 required: false,
55 type: "string"
56 },
57 filter: {
58 name: "filter",
59 in: "query",
60 description: "Filter resources as per the JSON:API specification",
61 required: false,
62 type: "string"
63 },
64 fields: {
65 name: "fields",
66 in: "query",
67 description: "Limit response payloads as per the JSON:API specification",
68 required: false,
69 type: "string"
70 },
71 page: {
72 name: "page",
73 in: "query",
74 description: "Pagination namespace",
75 required: false,
76 type: "string"
77 }
78 },
79 paths: { },
80 definitions: { }
81 };
82};
83

/home/pmcnr/work/jsonapi-server/lib/swagger/paths.js

100%
77
77
0
LineHitsSource
11"use strict";
21var swaggerPaths = module.exports = { };
31var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/swagger/../../");
41var _ = {
5 uniq: require("lodash.uniq")
6};
7
8
91swaggerPaths.getPathDefinitions = function() {
101 var paths = { };
11
121 for (var resourceName in jsonApi._resources) {
135 var resourceConfig = jsonApi._resources[resourceName];
145 swaggerPaths._addPathDefinition(paths, resourceConfig);
15 }
16
171 return paths;
18};
19
201swaggerPaths._addPathDefinition = function(paths, resourceConfig) {
215 if (!paths || !resourceConfig) return undefined;
225 var resourceName = resourceConfig.resource;
23
245 swaggerPaths._addBasicPaths(paths, resourceName, resourceConfig);
25
265 Object.keys(resourceConfig.attributes).filter(function(relationName) {
2744 var relation = resourceConfig.attributes[relationName];
2844 relation = relation._settings;
2981 if (!relation || relation.__as) return false;
307 relation = relation.__many || relation.__one;
317 return (jsonApi._resources[relation] && jsonApi._resources[relation].handlers.find);
32 }).forEach(function(relationName) {
337 var relation = resourceConfig.attributes[relationName];
347 relation = relation._settings.__one || relation._settings.__many;
35
367 swaggerPaths._addDeepPaths(paths, resourceName, resourceConfig, relationName, relation);
37 });
38};
39
401swaggerPaths._addBasicPaths = function(paths, resourceName, resourceConfig) {
415 paths["/" + resourceName] = {
42 get: swaggerPaths._getPathOperationObject({
43 handler: "search",
44 resourceName: resourceName,
45 description: "Search for " + resourceName,
46 parameters: resourceConfig.searchParams,
47 hasPathId: false
48 }),
49 post: swaggerPaths._getPathOperationObject({
50 handler: "create",
51 resourceName: resourceName,
52 description: "Create a new instance of " + resourceName,
53 parameters: resourceConfig.attributes,
54 hasPathId: false
55 })
56 };
57
585 paths["/" + resourceName + "/{id}"] = {
59 get: swaggerPaths._getPathOperationObject({
60 handler: "find",
61 resourceName: resourceName,
62 description: "Get a specific instance of " + resourceName,
63 hasPathId: true
64 }),
65 delete: swaggerPaths._getPathOperationObject({
66 handler: "delete",
67 resourceName: resourceName,
68 description: "Delete an instance of " + resourceName,
69 hasPathId: true
70 }),
71 patch: swaggerPaths._getPathOperationObject({
72 handler: "update",
73 resourceName: resourceName,
74 description: "Update an instance of " + resourceName,
75 hasPathId: true
76 })
77 };
78};
79
801swaggerPaths._addDeepPaths = function(paths, resourceName, resourceConfig, relationName, relation) {
817 paths["/" + resourceName + "/{id}/" + relationName] = {
82 get: swaggerPaths._getPathOperationObject({
83 handler: "find",
84 resourceName: relation,
85 hasPathId: true
86 })
87 };
88
897 var relationType = resourceConfig.attributes[relationName]._settings.__many ? "many" : "one";
907 paths["/" + resourceName + "/{id}/relationships/" + relationName] = {
91 get: swaggerPaths._getPathOperationObject({
92 handler: "find",
93 resourceName: relation,
94 relationType: relationType,
95 extraTags: resourceName,
96 hasPathId: true
97 }),
98 post: swaggerPaths._getPathOperationObject({
99 handler: "create",
100 resourceName: relation,
101 relationType: relationType,
102 extraTags: resourceName,
103 hasPathId: true
104 }),
105 patch: swaggerPaths._getPathOperationObject({
106 handler: "update",
107 resourceName: relation,
108 relationType: relationType,
109 extraTags: resourceName,
110 hasPathId: true
111 }),
112 delete: swaggerPaths._getPathOperationObject({
113 handler: "delete",
114 resourceName: relation,
115 relationType: relationType,
116 extraTags: resourceName,
117 hasPathId: true
118 })
119 };
120};
121
1221swaggerPaths._getPathOperationObject = function(options) {
12360 var pathDefinition = {
124 tags: [ options.resourceName ],
125 description: options.description,
126 parameters: [ ],
127 responses: {
128 "200": {
129 description: options.resourceName + " " + options.handler + " response",
130 schema: {
131 type: "object",
132 required: [ "jsonapi", "meta", "links" ],
133 properties: {
134 jsonapi: {
135 type: "object",
136 required: [ "version" ],
137 properties: {
138 version: {
139 type: "string"
140 }
141 }
142 },
143 meta: {
144 type: "object"
145 },
146 links: {
147 type: "object",
148 required: [ "self" ],
149 properties: {
150 self: {
151 type: "string"
152 },
153 first: {
154 type: "string"
155 },
156 last: {
157 type: "string"
158 },
159 next: {
160 type: "string"
161 },
162 prev: {
163 type: "string"
164 }
165 }
166 }
167 }
168 }
169 },
170 default: {
171 description: "Unexpected error",
172 schema: {
173 "$ref": "#/definitions/error"
174 }
175 }
176 }
177 };
17860 if (options.extraTags) {
17928 pathDefinition.tags = pathDefinition.tags.concat(options.extraTags);
18028 pathDefinition.tags = _.uniq(pathDefinition.tags);
181 }
182
18360 var responseShortcut = pathDefinition.responses["200"].schema.properties;
18460 responseShortcut.data = {
185 "$ref": "#/definitions/" + options.resourceName
186 };
187
18860 if (options.handler === "search") {
1895 responseShortcut.data = {
190 type: "array",
191 items: responseShortcut.data
192 };
193 }
19460 if (((options.handler === "search") || (options.handler === "find")) && !options.relation) {
19524 pathDefinition.parameters = pathDefinition.parameters.concat(swaggerPaths._optionalJsonApiParameters());
19624 responseShortcut.included = {
197 type: "array",
198 items: {
199 type: "object"
200 }
201 };
202 }
203
20460 if ((options.handler === "create") || (options.handler === "update")) {
20524 var body = swaggerPaths._getBaseResourceModel(options.resourceName);
20624 if (options.relationType) {
20714 body.schema.properties.data = swaggerPaths._getRelationModel();
20814 if ((options.handler === "update") && (options.relationType === "many")) {
2093 body.schema.properties.data = {
210 type: "array",
211 items: body.schema.properties.data
212 };
213 }
214 }
21524 pathDefinition.parameters = pathDefinition.parameters.concat(body);
216 }
217
21860 if (options.handler === "delete" && options.relationType) {
2197 var body2 = swaggerPaths._getBaseResourceModel(options.resourceName);
2207 body2.schema.properties.data = swaggerPaths._getRelationModel();
2217 pathDefinition.parameters = pathDefinition.parameters.concat(body2);
222 }
223
224
22560 if (options.handler === "delete") {
22612 responseShortcut.data = undefined;
227 }
228
22960 if (options.handler === "create") {
23012 pathDefinition.responses["201"] = pathDefinition.responses["200"];
23112 pathDefinition.responses["200"] = undefined;
232 }
233
23460 if (options.hasPathId) {
23550 pathDefinition.parameters.push({
236 name: "id",
237 in: "path",
238 description: "id of specific instance to lookup",
239 required: true,
240 type: "string"
241 });
242 }
243
24460 if (options.parameters) {
24510 var additionalParams = Object.keys(options.parameters).map(function(paramName) {
24675 var joiScheme = options.parameters[paramName];
24790 if ((paramName === "id") || (paramName === "type")) return null;
248
24960 return {
250 name: paramName,
251 in: "query",
252 description: joiScheme._description || undefined,
253 required: ((joiScheme._flags || { }).presence === "required"),
254 type: joiScheme._type
255 };
256 });
25710 pathDefinition.parameters.concat(additionalParams);
258 }
259
26060 if (options.relationType) {
26128 responseShortcut.data = swaggerPaths._getRelationModel();
26228 if (options.relationType === "many") {
26312 responseShortcut.data = {
264 type: "array",
265 items: responseShortcut.data
266 };
267 }
268 }
269
27060 return pathDefinition;
271};
272
2731swaggerPaths._optionalJsonApiParameters = function() {
27424 return [
275 { "$ref": "#/parameters/sort" },
276 { "$ref": "#/parameters/include" },
277 { "$ref": "#/parameters/filter" },
278 { "$ref": "#/parameters/fields" },
279 { "$ref": "#/parameters/page" }
280 ];
281};
282
2831swaggerPaths._getRelationModel = function() {
28449 return {
285 type: "object",
286 required: [ "type", "id" ],
287 properties: {
288 type: {
289 type: "string"
290 },
291 id: {
292 type: "string"
293 },
294 meta: {
295 type: "object"
296 }
297 }
298 };
299};
300
3011swaggerPaths._getBaseResourceModel = function(resourceName) {
30231 return {
303 in: "body",
304 name: "body",
305 description: "New or partial resource",
306 required: true,
307 schema: {
308 type: "object",
309 properties: {
310 data: {
311 "$ref": "#/definitions/" + resourceName
312 }
313 }
314 }
315 };
316};
317

/home/pmcnr/work/jsonapi-server/lib/swagger/resources.js

88%
42
37
5
LineHitsSource
11"use strict";
21var swaggerPaths = module.exports = { };
31var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/swagger/../../");
4
5
61swaggerPaths.getResourceDefinitions = function() {
71 var resourceDefinitions = { };
8
91 for (var resource in jsonApi._resources) {
105 resourceDefinitions[resource] = swaggerPaths._getResourceDefinition(jsonApi._resources[resource]);
11 }
121 resourceDefinitions.error = swaggerPaths._getErrorDefinition();
13
141 return resourceDefinitions;
15};
16
171swaggerPaths._getResourceDefinition = function(resourceConfig) {
185 if (Object.keys(resourceConfig.handlers || { }).length === 0) return undefined;
19
205 var resourceDefinition = {
21 description: resourceConfig.description,
22 type: "object",
23 // required: [ "id", "type", "attributes", "relationships", "links" ],
24 properties: {
25 "id": {
26 type: "string"
27 },
28 "type": {
29 type: "string"
30 },
31 "attributes": {
32 type: "object",
33 properties: { }
34 },
35 "relationships": {
36 type: "object",
37 properties: { }
38 },
39 "links": {
40 type: "object",
41 properties: {
42 self: {
43 type: "string"
44 }
45 }
46 },
47 "meta": {
48 type: "object"
49 }
50 }
51 };
525 var attributeShortcut = resourceDefinition.properties.attributes.properties;
535 var relationshipsShortcut = resourceDefinition.properties.relationships.properties;
54
55
565 var attributes = resourceConfig.attributes;
575 for (var attribute in attributes) {
5859 if ((attribute === "id") || (attribute === "type") || (attribute === "meta")) continue;
59
6029 var joiScheme = attributes[attribute];
61
6229 var swaggerScheme = { };
6329 if (joiScheme._description) {
6422 swaggerScheme.description = joiScheme._description;
65 }
66
6729 if (!joiScheme._settings) {
6816 swaggerScheme.type = joiScheme._type;
6916 if (swaggerScheme.type === "date") {
700 swaggerScheme.type = "string";
710 swaggerScheme.format = "date";
72 }
7316 attributeShortcut[attribute] = swaggerScheme;
74
7516 if ((joiScheme._flags || { }).presence === "required") {
763 resourceDefinition.properties.attributes.required = resourceDefinition.properties.attributes.required || [ ];
773 resourceDefinition.properties.attributes.required.push(attribute);
78 }
79 } else {
8013 if (joiScheme._settings.as) continue;
81
8213 swaggerScheme = {
83 type: "object",
84 properties: {
85 meta: {
86 type: "object"
87 },
88 links: {
89 type: "object",
90 properties: {
91 self: {
92 type: "string"
93 },
94 related: {
95 type: "string"
96 }
97 }
98 },
99 data: {
100 type: "object",
101 required: [ "type", "id" ],
102 properties: {
103 type: {
104 type: "string"
105 },
106 id: {
107 type: "string"
108 },
109 meta: {
110 type: "object"
111 }
112 }
113 }
114 }
115 };
116
11713 if (joiScheme._settings.__many) {
1188 swaggerScheme.properties.data = {
119 type: "array",
120 items: swaggerScheme.properties.data
121 };
122 }
123
12413 if ((joiScheme._flags || { }).presence === "required") {
1250 if (joiScheme._settings.__many) {
1260 swaggerScheme.required = true;
127 } else {
1280 swaggerScheme.required = [ "type", "id" ];
129 }
130 }
13113 relationshipsShortcut[attribute] = swaggerScheme;
132 }
133 }
134
1355 return resourceDefinition;
136};
137
1381swaggerPaths._getErrorDefinition = function() {
1391 return {
140 type: "object",
141 required: [ "jsonapi", "meta", "links", "errors" ],
142 properties: {
143 jsonapi: {
144 type: "object",
145 required: [ "version" ],
146 properties: {
147 version: {
148 type: "string"
149 }
150 }
151 },
152 meta: {
153 type: "object"
154 },
155 links: {
156 type: "object",
157 properties: {
158 self: {
159 type: "string"
160 }
161 }
162 },
163 errors: {
164 type: "array",
165 items: {
166 type: "object",
167 required: [
168 "status", "code", "title", "detail"
169 ],
170 properties: {
171 status: {
172 type: "string"
173 },
174 code: {
175 type: "string"
176 },
177 title: {
178 type: "string"
179 },
180 detail: {
181 type: "object"
182 }
183 }
184 }
185 }
186 }
187 };
188};
189