├── .gitignore ├── .npmrc ├── .eslintignore ├── .npmignore ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── index.js ├── config.js ├── libs ├── fumble.http.js ├── helper.js └── http-status.js ├── tests └── unit │ ├── index.js │ ├── libs │ ├── helper.js │ └── fumble.http.js │ └── HttpError.js ├── .eslintrc ├── HttpError.js ├── package.json ├── index.d.ts ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /artifacts/ 3 | *.log 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | package-lock = true 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # node_modules ignored by default 2 | build/** 3 | artifacts/** 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .eslint* 3 | .github/ 4 | /arrow-target/ 5 | /artifacts/ 6 | /coverage/ 7 | /tests/ 8 | lcov-* 9 | xunit* 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: webpack 11 | versions: 12 | - "> 1.15.0" 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | module.exports = { 9 | http: require('./libs/fumble.http'), 10 | HttpError: require('./HttpError') 11 | }; 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm install 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | module.exports = { 9 | http: { 10 | SUPPORTED_METHODS: [ 11 | // client errors 12 | 400, 13 | 401, 14 | 403, 15 | 404, 16 | 405, 17 | 407, 18 | 409, 19 | 410, 20 | 412, 21 | 429, 22 | 23 | // server errors 24 | 500, 25 | 501, 26 | 502, 27 | 503 28 | ] 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /libs/fumble.http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var HttpError = require('./../HttpError'); 9 | 10 | function create (status, message, options) { 11 | return new HttpError(status, message, options); 12 | } 13 | 14 | function generateMethod (status, defaultMessage) { 15 | return function createMethod (message, options) { 16 | return create(status, message || defaultMessage, options || {}); 17 | }; 18 | } 19 | 20 | var fumbleHttp = { 21 | create: create 22 | }; 23 | 24 | var config = require('./../config').http; 25 | var helper = require('./helper'); 26 | var SUPPORTED_METHODS = helper.mapHTTPStatusCodesToMessagesAndMethodNames(config.SUPPORTED_METHODS); 27 | 28 | SUPPORTED_METHODS.forEach(function eachMethod (supported) { 29 | var method = generateMethod(supported.status, supported.message); 30 | fumbleHttp[supported.method] = method; 31 | }); 32 | 33 | module.exports = fumbleHttp; 34 | -------------------------------------------------------------------------------- /libs/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var STATUS_CODES = require('./http-status'); 9 | var camelCase = function (str) { 10 | var parts = str.toLowerCase().split(/[\s-]/) 11 | 12 | var i; 13 | for (i = 1; i < parts.length; ++i) { 14 | parts[i] = parts[i][0].toUpperCase() + parts[i].substr(1); 15 | } 16 | 17 | return parts.join(''); 18 | } 19 | 20 | module.exports = { 21 | httpErrorMessageToMethodName: camelCase, 22 | mapHTTPStatusCodesToMessagesAndMethodNames: function (statusCodes) { 23 | var mapping = []; 24 | statusCodes.forEach(function (status) { 25 | var message = STATUS_CODES[status]; 26 | var method = camelCase(message); 27 | mapping.push({ 28 | status: status, 29 | message: message, 30 | method: method 31 | }); 32 | }); 33 | 34 | return mapping; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /tests/unit/index.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, before */ 2 | 3 | 'use strict'; 4 | 5 | var ROOT_DIR = require('path').resolve(__dirname, '../..'); 6 | var webpack = require('webpack'); 7 | 8 | var expect = require('chai').expect; 9 | 10 | describe('fumble', function () { 11 | var fumble; 12 | 13 | before(function () { 14 | fumble = require(ROOT_DIR); 15 | }); 16 | 17 | it('should provide an http object', function () { 18 | expect(fumble).to.have.property('http').that.is.an('object').and.is.not.empty; 19 | expect(fumble).to.have.nested.property('http.create').that.is.a('function'); 20 | }); 21 | 22 | it('should not error when requiring the webpack generated file', function (done) { 23 | var config = { 24 | entry: ROOT_DIR, 25 | output: { 26 | path: ROOT_DIR + '/artifacts/webpack-test', 27 | filename: 'fumble.webpack.js' 28 | } 29 | }; 30 | 31 | webpack(config, function () { 32 | require([config.output.path, config.output.filename].join('/')); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true 5 | }, 6 | 7 | "rules": { 8 | "consistent-this": [2, "self"], 9 | "guard-for-in": 2, 10 | "handle-callback-err": 2, 11 | "indent" : 2, 12 | "keyword-spacing": 2, 13 | "no-catch-shadow": 2, 14 | "no-else-return": 2, 15 | "no-floating-decimal": 2, 16 | "no-inline-comments": 1, 17 | "no-lonely-if": 2, 18 | "no-multiple-empty-lines": 2, 19 | "no-nested-ternary": 2, 20 | "no-self-compare": 2, 21 | "no-sync": 1, 22 | "no-throw-literal": 2, 23 | "no-underscore-dangle" : 0, 24 | "no-unused-expressions" : 1, 25 | "no-unused-vars" : [ 2, { "args" : "none" } ], 26 | "no-void": 2, 27 | "no-warning-comments": 1, 28 | "object-curly-spacing": 2, 29 | "padded-blocks" : [ 2, "never"], 30 | "quotes" : [ 2,"single"], 31 | "radix": 2, 32 | "spaced-comment": [2, "always"], 33 | "space-before-function-paren" : [ 2, "always"], 34 | "valid-jsdoc": [2], 35 | "vars-on-top": 1, 36 | "wrap-iife" : 2, 37 | "wrap-regex": 2 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /HttpError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var STATUS_CODES = require('./libs/http-status'); 9 | var DEFAULT_STATUS_CODE = 500; 10 | 11 | function HttpError (status, message, options) { 12 | options = options || {}; // defense 13 | 14 | // consider throwing if 'status' is not in STATUS_CODES 15 | if (!STATUS_CODES[status]) { 16 | status = DEFAULT_STATUS_CODE; 17 | } 18 | 19 | this.statusCode = status; 20 | this.message = message || STATUS_CODES[this.statusCode]; 21 | 22 | if (options.debug) { 23 | this.debug = options.debug; 24 | } 25 | 26 | // Stack Trace 27 | if (Error.captureStackTrace) { 28 | // if not IE 29 | Error.captureStackTrace(this, this.constructor); 30 | } else { 31 | // in IE 32 | this.stack = (new Error()).stack; 33 | } 34 | } 35 | 36 | // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Example.3A_Custom_Error_Types 37 | HttpError.prototype = new Error(); 38 | HttpError.prototype.constructor = HttpError; 39 | HttpError.prototype.name = 'HttpError'; 40 | 41 | module.exports = HttpError; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fumble", 3 | "description": "Simple error objects in node. Created specifically to be used with the fetchr library and based on hapi.js' Boom.", 4 | "version": "0.1.9", 5 | "author": "Mo Kouli ", 6 | "main": "index.js", 7 | "typings": "./index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "git@github.com:yahoo/fumble.git#master" 11 | }, 12 | "bugs": "https://github.com/yahoo/fumble/issues", 13 | "scripts": { 14 | "cover": "istanbul cover --dir artifacts -- _mocha tests/unit --recursive --reporter tap", 15 | "devtest": "mocha tests/unit --recursive --reporter nyan", 16 | "lint": "eslint .", 17 | "test": "npm run lint && npm run cover" 18 | }, 19 | "devDependencies": { 20 | "chai": "^4.3.4", 21 | "coveralls": "^3.1.1", 22 | "eslint": "^8.0.0", 23 | "istanbul": "^0.4.5", 24 | "mocha": "^10.0.0", 25 | "pre-commit": "^1.2.2", 26 | "webpack": "^5.48.0" 27 | }, 28 | "pre-commit": [ 29 | "lint", 30 | "devtest" 31 | ], 32 | "keywords": [ 33 | "yahoo", 34 | "fetchr", 35 | "error" 36 | ], 37 | "licenses": [ 38 | { 39 | "type": "BSD", 40 | "url": "https://github.com/yahoo/fumble/blob/master/LICENSE.md" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'fumble' { 2 | const http: { 3 | badGateway: (message?: string, options?: { debug?: any }) => HttpError; 4 | badRequest: (message?: string, options?: { debug?: any }) => HttpError; 5 | conflict: (message?: string, options?: { debug?: any }) => HttpError; 6 | create: (status?: number, message?: string, options?: { debug?: any }) => HttpError; 7 | forbidden: (message?: string, options?: { debug?: any }) => HttpError; 8 | gone: (message?: string, options?: { debug?: any }) => HttpError; 9 | internalServerError: (message?: string, options?: { debug?: any }) => HttpError; 10 | methodNotAllowed: (message?: string, options?: { debug?: any }) => HttpError; 11 | notFound: (message?: string, options?: { debug?: any }) => HttpError; 12 | notImplemented: (message?: string, options?: { debug?: any }) => HttpError; 13 | preconditionFailed: (message?: string, options?: { debug?: any }) => HttpError; 14 | proxyAuthenticationRequired: (message?: string, options?: { debug?: any }) => HttpError; 15 | serviceUnavailable:(message?: string, options?: { debug?: any }) => HttpError; 16 | tooManyRequests: (message?: string, options?: { debug?: any }) => HttpError; 17 | unauthorized: (message?: string, options?: { debug?: any }) => HttpError; 18 | }; 19 | 20 | class HttpError extends Error { 21 | debug?: any; 22 | statusCode: number; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | 3 | 4 | ## Copyright (c) 2015, Yahoo Inc. All rights reserved. 5 | 6 | Redistribution and use of this software in source and binary forms, with or 7 | without modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of Yahoo Inc. nor the names of YUI's contributors may be 16 | used to endorse or promote products derived from this software without 17 | specific prior written permission of Yahoo Inc. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /libs/http-status.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 100: 'Continue', 3 | 101: 'Switching Protocols', 4 | 102: 'Processing', 5 | 103: 'Early Hints', 6 | 200: 'OK', 7 | 201: 'Created', 8 | 202: 'Accepted', 9 | 203: 'Non-Authoritative Information', 10 | 204: 'No Content', 11 | 205: 'Reset Content', 12 | 206: 'Partial Content', 13 | 207: 'Multi Status', 14 | 208: 'Already Reported', 15 | 226: 'IM Used', 16 | 300: 'Multiple Choices', 17 | 301: 'Moved Permanently', 18 | 302: 'Found', 19 | 303: 'See Other', 20 | 304: 'Not Modified', 21 | 305: 'Use Proxy', 22 | 306: 'Switch Proxy', 23 | 307: 'Temporary Redirect', 24 | 308: 'Permanent Redirect', 25 | 400: 'Bad Request', 26 | 401: 'Unauthorized', 27 | 402: 'Payment Required', 28 | 403: 'Forbidden', 29 | 404: 'Not Found', 30 | 405: 'Method Not Allowed', 31 | 406: 'Not Acceptable', 32 | 407: 'Proxy Authentication Required', 33 | 408: 'Request Time-out', 34 | 409: 'Conflict', 35 | 410: 'Gone', 36 | 411: 'Length Required', 37 | 412: 'Precondition Failed', 38 | 413: 'Request Entity Too Large', 39 | 414: 'Request-URI Too Large', 40 | 415: 'Unsupported Media Type', 41 | 416: 'Requested Range not Satisfiable', 42 | 417: 'Expectation Failed', 43 | 418: 'I\'m a teapot', 44 | 421: 'Misdirected Request', 45 | 422: 'Unprocessable Entity', 46 | 423: 'Locked', 47 | 424: 'Failed Dependency', 48 | 426: 'Upgrade Required', 49 | 428: 'Precondition Required', 50 | 429: 'Too Many Requests', 51 | 431: 'Request Header Fields Too Large', 52 | 451: 'Unavailable For Legal Reasons', 53 | 500: 'Internal Server Error', 54 | 501: 'Not Implemented', 55 | 502: 'Bad Gateway', 56 | 503: 'Service Unavailable', 57 | 504: 'Gateway Time-out', 58 | 505: 'HTTP Version not Supported', 59 | 506: 'Variant Also Negotiates', 60 | 507: 'Insufficient Storage', 61 | 508: 'Loop Detected', 62 | 510: 'Not Extended', 63 | 511: 'Network Authentication Required' 64 | } 65 | -------------------------------------------------------------------------------- /tests/unit/libs/helper.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, before */ 2 | 3 | 'use strict'; 4 | 5 | var ROOT_DIR = require('path').resolve(__dirname, '../../..'); 6 | 7 | var expect = require('chai').expect; 8 | 9 | describe('helper', function () { 10 | var helper; 11 | 12 | var TEST_MAPPINGS = { 13 | 400: { 14 | message: 'Bad Request', 15 | method: 'badRequest' 16 | }, 17 | 403: { 18 | message: 'Forbidden', 19 | method: 'forbidden' 20 | }, 21 | 408: { 22 | message: 'Request Time-out', 23 | method: 'requestTimeOut' 24 | }, 25 | 414: { 26 | message: 'Request-URI Too Large', 27 | method: 'requestUriTooLarge' 28 | } 29 | }; 30 | 31 | before(function () { 32 | helper = require(ROOT_DIR + '/libs/helper'); 33 | }); 34 | 35 | describe('#httpErrorMessageToMethodName', function () { 36 | it('should return camelCased methods removing spaces and dashes', function () { 37 | Object.keys(TEST_MAPPINGS).forEach(function (status) { 38 | var err = TEST_MAPPINGS[status]; 39 | expect(helper.httpErrorMessageToMethodName(err.message)).to.equal(err.method); 40 | }); 41 | }); 42 | }); 43 | 44 | describe('#mapHTTPStatusCodesToMessagesAndMethodNames', function () { 45 | it('should return an array of objects with the correct status, message, and method props', function () { 46 | var statusCodes = Object.keys(TEST_MAPPINGS); 47 | var mappings = helper.mapHTTPStatusCodesToMessagesAndMethodNames(statusCodes); 48 | 49 | expect(mappings).to.be.an('array').of.length(statusCodes.length); 50 | 51 | mappings.forEach(function (mapping) { 52 | expect(mapping).to.be.an('object').and.have.property('status'); 53 | expect(statusCodes).to.include(mapping.status); 54 | 55 | expect(mapping).to.have.property('message').that.is.a('string'); 56 | expect(mapping).to.have.property('method', mapping.method); 57 | 58 | var err = TEST_MAPPINGS[mapping.status]; 59 | expect(mapping.message).to.equal(err.message); 60 | expect(mapping.method).to.equal(err.method); 61 | }); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/unit/HttpError.js: -------------------------------------------------------------------------------- 1 | /* globals describe, before, it */ 2 | 3 | 'use strict'; 4 | 5 | var ROOT_DIR = require('path').resolve(__dirname, '../..'); 6 | 7 | var expect = require('chai').expect; 8 | var STATUS_CODES = require(ROOT_DIR + '/libs/http-status'); 9 | 10 | describe('HttpError', function () { 11 | var HttpError; 12 | 13 | before(function () { 14 | HttpError = require(ROOT_DIR + '/HttpError'); 15 | }); 16 | 17 | it('should have a #name', function () { 18 | expect(HttpError).to.have.property('name', 'HttpError'); 19 | }); 20 | 21 | describe('#constructor', function () { 22 | var defaultError = { 23 | status: 500, 24 | message: STATUS_CODES[500] 25 | }; 26 | 27 | it('should be a constructor', function () { 28 | expect(new HttpError()).to.be.an.instanceof(HttpError); 29 | }); 30 | 31 | it('should extend Error', function () { 32 | expect(new HttpError()).to.be.an.instanceof(Error); 33 | }); 34 | 35 | it('should return the default status and error if passed nothing', function () { 36 | var error = new HttpError(); 37 | expect(error).to.be.an('object').and.have.property('statusCode', defaultError.status); 38 | expect(error).to.have.property('message', defaultError.message); 39 | }); 40 | 41 | it('should set the proper error message for the passed in status', function () { 42 | var error = new HttpError(400); 43 | expect(error).to.be.an('object').and.have.property('statusCode', 400); 44 | expect(error).to.have.property('message', STATUS_CODES[400]); 45 | }); 46 | 47 | it('should set a custom message', function () { 48 | var message = 'missing foo param when calling the bar service'; 49 | var error = new HttpError(400, message); 50 | expect(error).to.be.an('object').and.have.property('statusCode', 400); 51 | expect(error).to.have.property('message', message); 52 | }); 53 | 54 | it('should set the default status if passed an invalid status code', function () { 55 | var error = new HttpError(9001); 56 | expect(error).to.be.an('object').and.have.property('statusCode', defaultError.status); 57 | expect(error).to.have.property('message', defaultError.message); 58 | }); 59 | 60 | it('should set a debug prop if passed in as opts.debug', function () { 61 | var message = 'missing foo param when calling the bar service'; 62 | var debug = ['guid=1234', 'params=baz']; 63 | 64 | var error = new HttpError(400, message, { 65 | debug: debug 66 | }); 67 | 68 | expect(error).to.be.an('object').and.have.property('statusCode', 400); 69 | expect(error).to.have.property('message', message); 70 | expect(error).to.have.property('debug', debug).that.has.length(2); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/unit/libs/fumble.http.js: -------------------------------------------------------------------------------- 1 | /* globals describe, before, it */ 2 | 3 | 'use strict'; 4 | 5 | var ROOT_DIR = require('path').resolve(__dirname, '../../..'); 6 | 7 | var expect = require('chai').expect; 8 | 9 | var HTTP_STATUS_CODES = require(ROOT_DIR + '/libs/http-status'); 10 | var HttpError = require(ROOT_DIR + '/HttpError'); 11 | var config = require(ROOT_DIR + '/config').http; 12 | 13 | describe('libs/fumble.http', function () { 14 | var httpfumble; 15 | 16 | var SUPPORTS = [ 17 | 'badRequest', 18 | 'unauthorized', 19 | 'forbidden', 20 | 'notFound', 21 | 'methodNotAllowed', 22 | 'proxyAuthenticationRequired', 23 | 'conflict', 24 | 'gone', 25 | 'preconditionFailed', 26 | 'tooManyRequests', 27 | 'internalServerError', 28 | 'notImplemented', 29 | 'badGateway', 30 | 'serviceUnavailable' 31 | ]; 32 | 33 | before(function () { 34 | httpfumble = require(ROOT_DIR + '/libs/fumble.http'); 35 | }); 36 | 37 | describe('#create', function () { 38 | it('should return an HttpError instance', function () { 39 | var status = 400; 40 | var debug = ['param=bar']; 41 | 42 | var errors = [httpfumble.create(status, 'message foo', { 43 | debug: debug 44 | }), httpfumble.create(status, null, { 45 | debug: debug 46 | })]; 47 | 48 | errors.forEach(function (error) { 49 | expect(error).to.be.an.instanceof(HttpError).and.have.property('statusCode', status); 50 | expect(error).to.have.property('debug', debug).and.has.length(1); 51 | }); 52 | 53 | expect(errors[0]).to.have.property('message', 'message foo'); 54 | expect(errors[1]).to.have.property('message', HTTP_STATUS_CODES[status]); 55 | }); 56 | }); 57 | 58 | SUPPORTS.forEach(function (method, index) { 59 | describe('#' + method, function () { 60 | var status = config.SUPPORTED_METHODS[index]; 61 | var message = HTTP_STATUS_CODES[status]; 62 | 63 | it('should provide a ' + method + ' method', function () { 64 | expect(httpfumble).to.respondTo(method); 65 | }); 66 | 67 | it('should return an HttpError instance', function () { 68 | expect(httpfumble[method]()).to.be.an.instanceof(HttpError); 69 | }); 70 | 71 | it('should have the proper status code and message', function () { 72 | var error = httpfumble[method](); 73 | expect(error).to.deep.equal(new HttpError(status, message)); 74 | }); 75 | 76 | it('should be able to set a custom message', function () { 77 | var error = httpfumble[method]('foo'); 78 | expect(error).to.deep.equal(new HttpError(status, 'foo')); 79 | }); 80 | 81 | it('should set a debug prop if passed in as opts.debug', function () { 82 | var debug = ['foo=bar', 'foobar']; 83 | var error = httpfumble[method](null, { 84 | debug: debug 85 | }); 86 | 87 | expect(error).to.deep.equal(new HttpError(status, null, { 88 | debug: debug 89 | })); 90 | }); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fumble 2 | 3 | [![npm version](https://badge.fury.io/js/fumble.svg)](http://badge.fury.io/js/fumble) 4 | ![Build Status](https://github.com/yahoo/fumble/actions/workflows/test.yml/badge.svg) 5 | 6 | [![Join the chat at https://gitter.im/yahoo/fumble](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/yahoo/fumble?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | Simple error objects in node. Created specifically to be used with the [fetchr](https://github.com/yahoo/fetchr) library and based on [hapi.js](http://hapijs.com/)' [Boom](https://github.com/hapijs/boom). 9 | 10 | ![eli manning](http://media2.giphy.com/media/F9AU77Krzw8ta/giphy.gif) 11 | 12 | ## Usage 13 | 14 | ```js 15 | import fumble from 'fumble'; 16 | import callAndProcess from './callAndProcess'; 17 | import api from 'api'; 18 | 19 | export default api.base.service({ 20 | name: 'foo', 21 | read: function (req, resource, params, context, callback) { 22 | switch(resource) { 23 | case this.name: 24 | callAndProcess(req, params, context, callback); 25 | return; 26 | } 27 | 28 | const error = fumble.http.create(400, 'Passed in an invalid resource', { 29 | debug: [resource] 30 | }); 31 | 32 | req.error(error); 33 | req.debug(error.stack); // nice stack trace 34 | /** 35 | * logs: 36 | * { [HttpError: Bad Request] statusCode: 400, message: 37 | * 'Passed in an invalid resource', debug: [ resource ] } 38 | */ 39 | 40 | callback(error); 41 | } 42 | }); 43 | ``` 44 | 45 | ## API Docs 46 | 47 | ### fumble.http 48 | 49 | provides a set of utilities for returning HTTP errors. Each method returns an `HttpError` instance, which itself extends the native `Error` class (which means you can access the `stack` prop on your error instance). Each error has the following two props: 50 | 51 | * `statusCode` {Number} - the HTTP status code (typically 4xx or 5xx). 52 | * `message` {String} - the error message 53 | 54 | #### `fumble.http.create ([status=500], [message='Internal Server Error'], [options])` 55 | 56 | Generate an `HttpError` object where: 57 | 58 | * `statusCode` - an HTTP error code number. Must be greater or equal 400 59 | * `message` - optional message string. 60 | * `options` - extra options 61 | * `options.debug` - additional error debug info set to `error.debug` property. 62 | 63 | ```js 64 | const error = fumble.http.create(400, 'missing params', { debug: [passedInParams] }); 65 | ``` 66 | 67 | #### HTTP 4xx Errors 68 | 69 | ##### `fumble.http.badRequest ([message='Bad Request'], [options])` 70 | 71 | returns an HTTP status code of **400** 72 | 73 | * `message` - optional message string. 74 | * `options` - extra options 75 | * `options.debug` - additional error debug info set to `error.debug` property. 76 | 77 | ```js 78 | fumble.http.badRequest('invalid query'); 79 | 80 | // essentially generates 81 | { 82 | statusCode: 400, 83 | message: 'invalid query' 84 | } 85 | ``` 86 | 87 | ##### `fumble.http.unauthorized ([message='Unauthorized'], [options])` 88 | 89 | returns an HTTP status code of **401** 90 | 91 | * `message` - optional message string. 92 | * `options` - extra options 93 | * `options.debug` - additional error debug info set to `error.debug` property. 94 | 95 | ```js 96 | fumble.http.unauthorized('not logged in'); 97 | 98 | // essentially generates 99 | { 100 | statusCode: 401, 101 | message: 'not logged in' 102 | } 103 | ``` 104 | 105 | ##### `fumble.http.forbidden ([message='Forbidden'], [options])` 106 | 107 | returns an HTTP status code of **403** 108 | 109 | * `message` - optional message string. 110 | * `options` - extra options 111 | * `options.debug` - additional error debug info set to `error.debug` property. 112 | 113 | ```js 114 | fumble.http.forbidden('top secret'); 115 | 116 | // essentially generates 117 | { 118 | statusCode: 403, 119 | message: 'top secret' 120 | } 121 | ``` 122 | 123 | ##### `fumble.http.notFound ([message='Not Found'], [options])` 124 | 125 | returns an HTTP status code of **404** 126 | 127 | * `message` - optional message string. 128 | * `options` - extra options 129 | * `options.debug` - additional error debug info set to `error.debug` property. 130 | 131 | ```js 132 | fumble.http.notFound('does not exist'); 133 | 134 | // essentially generates 135 | { 136 | statusCode: 404, 137 | message: 'does not exist' 138 | } 139 | ``` 140 | 141 | ##### `fumble.http.methodNotAllowed ([message='Method Not Allowed'], [options])` 142 | 143 | returns an HTTP status code of **405** 144 | 145 | * `message` - optional message string. 146 | * `options` - extra options 147 | * `options.debug` - additional error debug info set to `error.debug` property. 148 | 149 | ```js 150 | fumble.http.methodNotAllowed('not allowed'); 151 | 152 | // essentially generates 153 | { 154 | statusCode: 405, 155 | message: 'not allowed' 156 | } 157 | ``` 158 | 159 | ##### `fumble.http.proxyAuthenticationRequired ([message='Proxy Authentication Required'], [options])` 160 | 161 | returns an HTTP status code of **407** 162 | 163 | * `message` - optional message string. 164 | * `options` - extra options 165 | * `options.debug` - additional error debug info set to `error.debug` property. 166 | 167 | ```js 168 | fumble.http.proxyAuthenticationRequired('need to login to foo'); 169 | 170 | // essentially generates 171 | { 172 | statusCode: 407, 173 | message: 'need to login to foo' 174 | } 175 | ``` 176 | 177 | ##### `fumble.http.conflict ([message='Conflict'], [options])` 178 | 179 | returns an HTTP status code of **409** 180 | 181 | * `message` - optional message string. 182 | * `options` - extra options 183 | * `options.debug` - additional error debug info set to `error.debug` property. 184 | 185 | ```js 186 | fumble.http.conflict('collision detected'); 187 | 188 | // essentially generates 189 | { 190 | statusCode: 409, 191 | message: 'collision detected' 192 | } 193 | ``` 194 | 195 | ##### `fumble.http.gone ([message='Gone'], [options])` 196 | 197 | returns an HTTP status code of **410** 198 | 199 | * `message` - optional message string. 200 | * `options` - extra options 201 | * `options.debug` - additional error debug info set to `error.debug` property. 202 | 203 | ```js 204 | fumble.http.gone('bye bye'); 205 | 206 | // essentially generates 207 | { 208 | statusCode: 410, 209 | message: 'bye bye' 210 | } 211 | ``` 212 | 213 | ##### `fumble.http.preconditionFailed ([message='Precondition Failed'], [options])` 214 | 215 | returns an HTTP status code of **412** 216 | 217 | * `message` - optional message string. 218 | * `options` - extra options 219 | * `options.debug` - additional error debug info set to `error.debug` property. 220 | 221 | ```js 222 | fumble.http.preconditionFailed('missing CLA'); 223 | 224 | // essentially generates 225 | { 226 | statusCode: 412, 227 | message: 'missing CLA' 228 | } 229 | ``` 230 | 231 | ##### `fumble.http.tooManyRequests ([message='Too Many Requests'], [options])` 232 | 233 | returns an HTTP status code of **429** 234 | 235 | * `message` - optional message string. 236 | * `options` - extra options 237 | * `options.debug` - additional error debug info set to `error.debug` property. 238 | 239 | ```js 240 | fumble.http.tooManyRequests('slow down'); 241 | 242 | // essentially generates 243 | { 244 | statusCode: 429, 245 | message: 'slow down' 246 | } 247 | ``` 248 | 249 | #### HTTP 5xx Errors 250 | 251 | ##### `fumble.http.internalServerError ([message='Internal Server Error'], [options])` 252 | 253 | returns an HTTP status code of **500** 254 | 255 | * `message` - optional message string. 256 | * `options` - extra options 257 | * `options.debug` - additional error debug info set to `error.debug` property. 258 | 259 | ```js 260 | fumble.http.internalServerError('unkown error'); 261 | 262 | // essentially generates 263 | { 264 | statusCode: 500, 265 | message: 'unknown error' 266 | } 267 | ``` 268 | 269 | === 270 | 271 | ##### `fumble.http.notImplemented ([message='Not Implemented'], [options])` 272 | 273 | returns an HTTP status code of **501** 274 | 275 | * `message` - optional message string. 276 | * `options` - extra options 277 | * `options.debug` - additional error debug info set to `error.debug` property. 278 | 279 | ```js 280 | fumble.http.notImplemented('missing enhancement'); 281 | 282 | // essentially generates 283 | { 284 | statusCode: 501, 285 | message: 'missing enhancement' 286 | } 287 | ``` 288 | 289 | ##### `fumble.http.badGateway ([message='Bad Gateway'], [options])` 290 | 291 | returns an HTTP status code of **502** 292 | 293 | * `message` - optional message string. 294 | * `options` - extra options 295 | * `options.debug` - additional error debug info set to `error.debug` property. 296 | 297 | ```js 298 | fumble.http.badGateway('mongo error'); 299 | 300 | // essentially generates 301 | { 302 | statusCode: 502, 303 | message: 'mongo error' 304 | } 305 | ``` 306 | 307 | ##### `fumble.http.serviceUnavailable ([message='Service Unavailable'], [options]) 308 | 309 | returns an HTTP status code of **503** 310 | 311 | * `message` - optional message string. 312 | * `options` - extra options 313 | * `options.debug` - additional error debug info set to `error.debug` property. 314 | 315 | ```js 316 | fumble.http.serviceUnavailable('feeds are down'); 317 | 318 | // essentially generates 319 | { 320 | statusCode: 503, 321 | message: 'feeds are down' 322 | } 323 | ``` 324 | 325 | ## License 326 | 327 | This software is free to use under the Yahoo Inc. BSD license. 328 | See the [LICENSE file][] for license text and copyright information. 329 | 330 | [LICENSE file]: https://github.com/yahoo/fumble/blob/master/LICENSE.md 331 | --------------------------------------------------------------------------------