Skip to content

Commit

Permalink
Merge pull request pencilblue#1185 from pencilblue/feature/970
Browse files Browse the repository at this point in the history
Fixes pencilblue#970 - Improved Routing Capabilities
  • Loading branch information
brianhyder authored Dec 11, 2016
2 parents dc525c4 + 253e5ac commit 2e979f6
Show file tree
Hide file tree
Showing 32 changed files with 3,022 additions and 360 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_js:
- "4"
- "5"
- "6"
- "7"
- "iojs"
after_script:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
Expand Down
1 change: 1 addition & 0 deletions controllers/base_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ module.exports = function BaseControllerModule(pb) {

/**
* Parses the incoming payload of a request as JSON formatted data.
* @deprecated Since 0.8.0. Will be removed in v1.0
* @method getJSONPostParams
* @param {Function} cb
*/
Expand Down
2 changes: 1 addition & 1 deletion controllers/error_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports = function(pb) {
* @property error
* @type {Error}
*/
self.error = context.error;
self.error = context.error || self.error;

/**
*
Expand Down
102 changes: 102 additions & 0 deletions include/error/error_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright (C) 2016 PencilBlue, LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

//dependencies
var HttpStatusCodes = require('http-status-codes');

/**
* Provides convenience functions to create errors for specific conditions
* @class ErrorUtils
*/
class ErrorUtils {

/**
* Creates an error that represents when a resource is not found (404)
* @static
* @method notFound
* @param {string} [message]
* @returns {Error}
*/
static notFound (message) {
return ErrorUtils.custom(message, HttpStatusCodes.NOT_FOUND);
}

/**
* Creates an error that represents a lack of permission (403)
* @static
* @method forbidden
* @param {string} [message]
* @returns {Error}
*/
static forbidden (message) {
return ErrorUtils.custom(message, HttpStatusCodes.FORBIDDEN);
}

/**
* Creates an error that represents an unauthorized request (401)
* @static
* @method notAuthorized
* @param {string} [message]
* @returns {Error}
*/
static notAuthorized (message) {
return ErrorUtils.custom(message, HttpStatusCodes.UNAUTHORIZED);
}

/**
* Creates an error that represents an internal server error
* @static
* @method badRequest
* @param {object} [options]
* @param {string} [options.message]
* @param {Array} [options.validationErrors]
* @returns {Error}
*/
static badRequest (options) {
options = options || {};
var err = ErrorUtils.custom(options.message, HttpStatusCodes.BAD_REQUEST);
err.validationErrors = options.validationErrors;
return err;
}

/**
* Creates an error that represents an internal server error (500)
* @static
* @method internalServerError
* @param {string} [message]
* @returns {Error}
*/
static internalServerError (message) {
return ErrorUtils.custom(message);
}

/**
* Creates a custom error with a specific message and status code
* @param {string} [message='An Error Occurred']
* @param {Number} [code=500]
* @return {Error}
*/
static custom (message, code) {
code = code || HttpStatusCodes.INTERNAL_SERVER_ERROR;
var err = new Error(message || HttpStatusCodes.getStatusText(code) || 'An Error Occurred');
err.code = code;
return err;
}
}

module.exports = ErrorUtils;
90 changes: 22 additions & 68 deletions include/error/formatters/error_formatters.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

//dependencies
var path = require('path');
var HtmlEncoder = require('htmlencode');
var HttpStatusCodes = require('http-status-codes');
var XmlErrorFormatter = require('./xml_error_formatter');

module.exports = function(pb) {

Expand All @@ -42,16 +43,6 @@ module.exports = function(pb) {
*/
var DEFAULT_MIME = 'text/html';

/**
* Error code when a validation failure occurs
* @private
* @static
* @readonly
* @property BAD_REQUEST
* @type {Integer}
*/
var BAD_REQUEST = 400;

/**
* Converts an error to a plain object that can be serialized
* @private
Expand All @@ -68,7 +59,7 @@ module.exports = function(pb) {
if (pb.config.logging.showErrors) {
content.stack = params.error.stack;
}
if (params.error.code === BAD_REQUEST) {
if (params.error.code === HttpStatusCodes.BAD_REQUEST) {
delete content.stack;
content.validationErrors = params.error.validationErrors;
}
Expand Down Expand Up @@ -125,13 +116,18 @@ module.exports = function(pb) {
}

//let the default error controller handle it.
var code = params.error.code || 500;
var code = params.error.code || HttpStatusCodes.INTERNAL_SERVER_ERROR;
var ErrorController = null;
var paths = [
path.join(pb.config.docRoot, 'plugins', params.activeTheme, 'controllers/error', code + '.js'),
path.join(pb.config.docRoot, 'plugins', params.activeTheme, 'controllers/error/index.js'),
path.join(pb.config.docRoot, 'controllers/error_controller.js')
path.join(pb.config.docRoot, 'plugins', params.activeTheme, 'controllers/error/index.js')
];
if (params.activeTheme !== pb.config.plugins.default) {
paths.push(path.join(pb.config.docRoot, 'plugins', params.activeTheme, 'controllers/error', code + '.js'));
}
paths.push(path.join(pb.config.docRoot, 'controllers/error_controller.js'));

//iterate over paths until you find a good one
for (var i = 0; i < paths.length; i++) {
if (failedControllerPaths[paths[i]]) {
//we've seen it and it didn't exist or had a syntax error. Moving on!
Expand All @@ -146,20 +142,16 @@ module.exports = function(pb) {
catch(e){

//we failed so make sure don't do that again...
failedControllerPaths[paths[i]];
failedControllerPaths[paths[i]] = true;
}
}
var cInstance = new ErrorController();
var context = {
pathVars: {},
cInstance: cInstance,
themeRoute: {},
activeTheme: params.activeTheme,
initParams: {
error: params.error
}
};
params.reqHandler.doRender(context);
params.request.controllerInstance = new ErrorController();
params.request.controllerInstance.error = params.error;
params.request.themeRoute = params.request.themeRoute || {};
params.request.routeTheme = params.request.routeTheme || {};
params.request.siteObj = params.request.siteObj || pb.SiteService.getGlobalSiteContext();
params.request.themeRoute.handler = 'render';
params.request.router.continueAfter('parseRequestBody');
};

/**
Expand All @@ -175,46 +167,8 @@ module.exports = function(pb) {
*/
ErrorFormatters.xml = function(params, cb) {

var xmlObj = function(key, obj) {
var xml = '<' + HtmlEncoder.htmlEncode(key) + '>';
util.forEach(obj, function(val, key) {

if (util.isArray(val)) {
xml += xmlArray(key, val);
}
else if (util.isObject(val)) {
xml += xmlObj(key, val);
}
else {
xml += '<'+HtmlEncoder.htmlEncode(key)+'>' + HtmlEncoder.htmlEncode(val + '') + '</'+HtmlEncoder.htmlEncode(key)+'>';
}
});
xml += '</'+HtmlEncoder.htmlEncode(key)+'>';
return xml;
};
var xmlArray = function(key, obj) {
var xml = '';
util.forEach(obj, function(val, i) {

if (util.isArray(val)) {
xml += xmlArray(key+'_'+i, val);
}
else if (util.isObject(val)) {
xml += xmlObj(key, val);
}
else {
xml += '<'+HtmlEncoder.htmlEncode(key)+'>' + HtmlEncoder.htmlEncode(val + '') + '</'+HtmlEncoder.htmlEncode(key)+'>';
}
});
return xml;
};
var xmlPrimitive = function(key, val) {
return '<'+HtmlEncoder.htmlEncode(key)+'>' + HtmlEncoder.htmlEncode(val + '') + '</'+HtmlEncoder.htmlEncode(key)+'>';
};
cb(
null,
xmlObj('error', convertToObject(params))
);
var objToSerialize = convertToObject(params);
cb(null, XmlErrorFormatter.serialize(objToSerialize));
};

/**
Expand Down Expand Up @@ -326,7 +280,7 @@ module.exports = function(pb) {
'text/json': ErrorFormatters.json,
'text/html': ErrorFormatters.html,
'application/xml': ErrorFormatters.xml,
'text/xml': ErrorFormatters.xml,
'text/xml': ErrorFormatters.xml
});

/**
Expand Down
118 changes: 118 additions & 0 deletions include/error/formatters/xml_error_formatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
Copyright (C) 2016 PencilBlue, LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

//dependencies
var HtmlEncoder = require('htmlencode');

/**
* Responsible for formatting an error as XML
* @class XmlErrorFormatter
*/
class XmlErrorFormatter {

/**
* Serializes the object version of the error to be formatted
* @method serialize
* @param {object} params
* @returns {string}
*/
static serialize(params) {
return XmlErrorFormatter.serializeXmlObject('error', params);
}

/**
* @static
* @method serializeXmlPrimitive
* @param {string} key
* @param {*} val
* @returns {string}
*/
static serializeXmlPrimitive (key, val) {
return XmlErrorFormatter.startElement(key) + HtmlEncoder.htmlEncode(val + '') + XmlErrorFormatter.endElement(key);
}

/**
* @static
* @method serializeXmlArray
* @param {string} key
* @param {Array} obj
* @returns {string}
*/
static serializeXmlArray (key, obj) {
var xml = '';
obj.forEach(function(prop, i) {
var val = obj[prop];
if (Array.isArray(val)) {
xml += XmlErrorFormatter.serializeXmlArray(key + '_' + i, val);
}
else if (typeof val === 'object') {
xml += XmlErrorFormatter.serializeXmlObject(key, val);
}
else {
xml += XmlErrorFormatter.serializeXmlPrimitive(key, val);
}
});
return xml;
}

/**
* @static
* @method serializeXmlObject
* @param {string} key
* @param {object} obj
* @returns {string}
*/
static serializeXmlObject (key, obj) {
var xml = XmlErrorFormatter.startElement(key);
Object.keys(obj).forEach(function(val) {
if (Array.isArray(val)) {
xml += XmlErrorFormatter.serializeXmlArray(key, val);
}
else if (typeof val === 'object') {
xml += XmlErrorFormatter.serializeXmlObject(key, val);
}
else {
xml += XmlErrorFormatter.serializeXmlPrimitive(key, val);
}
});
xml += XmlErrorFormatter.endElement(key);
return xml;
}

/**
* @static
* @method startElement
* @param {string} key
* @returns {string}
*/
static startElement (key) {
return '<' + HtmlEncoder.htmlEncode(key) + '>';
}

/**
* @static
* @method endElement
* @param {string} key
* @returns {string}
*/
static endElement (key) {
return '</' + HtmlEncoder.htmlEncode(key) + '>';
}
}

module.exports = XmlErrorFormatter;
Loading

0 comments on commit 2e979f6

Please sign in to comment.