├── .editorconfig ├── .github ├── contributing.md ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .istanbul.yml ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── handler.js ├── lib ├── error-handler.js ├── index.js ├── not-found-handler.js └── public │ ├── 401.html │ ├── 404.html │ └── default.html ├── mocha.opts ├── not-found.js ├── package-lock.json ├── package.json └── test ├── error-handler.test.js ├── index.test.js └── not-found-handler.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Feathers 2 | 3 | Thank you for contributing to Feathers! :heart: :tada: 4 | 5 | This repo is the main core and where most issues are reported. Feathers embraces modularity and is broken up across many repos. To make this easier to manage we currently use [Zenhub](https://www.zenhub.com/) for issue triage and visibility. They have a free browser plugin you can install so that you can see what is in flight at any time, but of course you also always see current issues in Github. 6 | 7 | ## Report a bug 8 | 9 | Before creating an issue please make sure you have checked out the docs, specifically the [FAQ](https://docs.feathersjs.com/help/faq.html) section. You might want to also try searching Github. It's pretty likely someone has already asked a similar question. 10 | 11 | If you haven't found your answer please feel free to join our [slack channel](http://slack.feathersjs.com), create an issue on Github, or post on [Stackoverflow](http://stackoverflow.com) using the `feathers` or `feathersjs` tag. We try our best to monitor Stackoverflow but you're likely to get more immediate responses in Slack and Github. 12 | 13 | Issues can be reported in the [issue tracker](https://github.com/feathersjs/feathers/issues). Since feathers combines many modules it can be hard for us to assess the root cause without knowing which modules are being used and what your configuration looks like, so **it helps us immensely if you can link to a simple example that reproduces your issue**. 14 | 15 | ## Report a Security Concern 16 | 17 | We take security very seriously at Feathers. We welcome any peer review of our 100% open source code to ensure nobody's Feathers app is ever compromised or hacked. As a web application developer you are responsible for any security breaches. We do our very best to make sure Feathers is as secure as possible by default. 18 | 19 | In order to give the community time to respond and upgrade we strongly urge you report all security issues to us. Send one of the core team members a PM in [Slack](http://slack.feathersjs.com) or email us at hello@feathersjs.com with details and we will respond ASAP. 20 | 21 | For full details refer to our [Security docs](https://docs.feathersjs.com/SECURITY.html). 22 | 23 | ## Pull Requests 24 | 25 | We :heart: pull requests and we're continually working to make it as easy as possible for people to contribute, including a [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) and a [common test suite](https://github.com/feathersjs/feathers-service-tests) for database adapters. 26 | 27 | We prefer small pull requests with minimal code changes. The smaller they are the easier they are to review and merge. A core team member will pick up your PR and review it as soon as they can. They may ask for changes or reject your pull request. This is not a reflection of you as an engineer or a person. Please accept feedback graciously as we will also try to be sensitive when providing it. 28 | 29 | Although we generally accept many PRs they can be rejected for many reasons. We will be as transparent as possible but it may simply be that you do not have the same context or information regarding the roadmap that the core team members have. We value the time you take to put together any contributions so we pledge to always be respectful of that time and will try to be as open as possible so that you don't waste it. :smile: 30 | 31 | **All PRs (except documentation) should be accompanied with tests and pass the linting rules.** 32 | 33 | ### Code style 34 | 35 | Before running the tests from the `test/` folder `npm test` will run ESlint. You can check your code changes individually by running `npm run lint`. 36 | 37 | ### ES6 compilation 38 | 39 | Feathers uses [Babel](https://babeljs.io/) to leverage the latest developments of the JavaScript language. All code and samples are currently written in ES2015. To transpile the code in this repository run 40 | 41 | > npm run compile 42 | 43 | __Note:__ `npm test` will run the compilation automatically before the tests. 44 | 45 | ### Tests 46 | 47 | [Mocha](http://mochajs.org/) tests are located in the `test/` folder and can be run using the `npm run mocha` or `npm test` (with ESLint and code coverage) command. 48 | 49 | ### Documentation 50 | 51 | Feathers documentation is contained in Markdown files in the [feathers-docs](https://github.com/feathersjs/feathers-docs) repository. To change the documentation submit a pull request to that repo, referencing any other PR if applicable, and the docs will be updated with the next release. 52 | 53 | ## External Modules 54 | 55 | If you're written something awesome for Feathers, the Feathers ecosystem, or using Feathers please add it to the [showcase](https://docs.feathersjs.com/why/showcase.html). You also might want to check out the [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) that can be used to scaffold plugins to be Feathers compliant from the start. 56 | 57 | If you think it would be a good core module then please contact one of the Feathers core team members in [Slack](http://slack.feathersjs.com) and we can discuss whether it belongs in core and how to get it there. :beers: 58 | 59 | ## Contributor Code of Conduct 60 | 61 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 62 | 63 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 64 | 65 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 66 | 67 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 68 | 69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 72 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Steps to reproduce 2 | 3 | (First please check that this issue is not already solved as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#report-a-bug)) 5 | 6 | - [ ] Tell us what broke. The more detailed the better. 7 | - [ ] If you can, please create a simple example that reproduces the issue and link to a gist, jsbin, repo, etc. 8 | 9 | ### Expected behavior 10 | Tell us what should happen 11 | 12 | ### Actual behavior 13 | Tell us what happens instead 14 | 15 | ### System configuration 16 | 17 | Tell us about the applicable parts of your setup. 18 | 19 | **Module versions** (especially the part that's not working): 20 | 21 | **NodeJS version**: 22 | 23 | **Operating System**: 24 | 25 | **Browser Version**: 26 | 27 | **React Native Version**: 28 | 29 | **Module Loader**: -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | (If you have not already please refer to the contributing guideline as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#pull-requests)) 5 | 6 | - [ ] Tell us about the problem your pull request is solving. 7 | - [ ] Are there any open issues that are related to this? 8 | - [ ] Is this PR dependent on PRs in other repos? 9 | 10 | If so, please mention them to keep the conversations linked together. 11 | 12 | ### Other Information 13 | 14 | If there's anything else that's important and relevant to your pull 15 | request, mention that information here. This could include 16 | benchmarks, or other information. 17 | 18 | Your PR will be reviewed by a core team member and they will work with you to get your changes merged in a timely manner. If merged your PR will automatically be added to the changelog in the next release. 19 | 20 | If your changes involve documentation updates please mention that and link the appropriate PR in [feathers-docs](https://github.com/feathersjs/feathers-docs). 21 | 22 | Thanks for contributing to Feathers! :heart: -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Commenting this out is preferred by some people, see 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Users Environment Variables 30 | .lock-wscript 31 | 32 | dist/ 33 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | verbose: false 2 | instrumentation: 3 | root: ./lib/ 4 | include-all-sources: true 5 | reporting: 6 | print: summary 7 | reports: 8 | - html 9 | - text 10 | - lcov 11 | watermarks: 12 | statements: [50, 80] 13 | lines: [50, 80] 14 | functions: [50, 80] 15 | branches: [50, 80] 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .jshintrc 3 | .travis.yml 4 | .istanbul.yml 5 | .babelrc 6 | .idea/ 7 | .vscode/ 8 | test/ 9 | coverage/ 10 | .github/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - '6' 6 | install: npm install 7 | before_script: 8 | - npm install -g codeclimate-test-reporter 9 | after_script: 10 | - codeclimate-test-reporter < coverage/lcov.info 11 | addons: 12 | code_climate: 13 | repo_token: 39c710513d49b85f9cd9360fae42a7019691b96d639a5e95caf3116a09f455f6 14 | notifications: 15 | email: false 16 | slack: 17 | rooms: 18 | secure: A45tmvXAxLq82nbr8vazo30OpiDo1UMKkmMzmCjUfGjJ7dcAspCmOZOg/qpj5Pe/gKI5uGNq7/cF+JWMXVxC+dirndl+eo1WQrDcR5IzAUe0Zku43SoK19hFYpZy4zSL6z4bAL03Id4Sst0wSH0tnAyvagPASlaUAH4EM//fZ1I= 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v3.3.0](https://github.com/feathersjs/errors/tree/v3.3.0) (2018-02-12) 4 | [Full Changelog](https://github.com/feathersjs/errors/compare/v3.2.2...v3.3.0) 5 | 6 | **Closed issues:** 7 | 8 | - How to handling error from Hook function when I use Aync/Await in Hook function [\#106](https://github.com/feathersjs/errors/issues/106) 9 | 10 | **Merged pull requests:** 11 | 12 | - Add a verbose flag to notFound handler [\#107](https://github.com/feathersjs/errors/pull/107) ([daffl](https://github.com/daffl)) 13 | - Add req.url to notFound handler message [\#105](https://github.com/feathersjs/errors/pull/105) ([FreeLineTM](https://github.com/FreeLineTM)) 14 | 15 | ## [v3.2.2](https://github.com/feathersjs/errors/tree/v3.2.2) (2018-01-23) 16 | [Full Changelog](https://github.com/feathersjs/errors/compare/v3.2.1...v3.2.2) 17 | 18 | **Closed issues:** 19 | 20 | - Handling Status Codes [\#103](https://github.com/feathersjs/errors/issues/103) 21 | - Override default error page [\#102](https://github.com/feathersjs/errors/issues/102) 22 | - wrong npm package in Installation instructions [\#100](https://github.com/feathersjs/errors/issues/100) 23 | 24 | **Merged pull requests:** 25 | 26 | - Fix instanceof and prototypical inheritance [\#104](https://github.com/feathersjs/errors/pull/104) ([nikaspran](https://github.com/nikaspran)) 27 | - Update mocha to the latest version 🚀 [\#101](https://github.com/feathersjs/errors/pull/101) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 28 | - fix installation declaration [\#99](https://github.com/feathersjs/errors/pull/99) ([jasonmacgowan](https://github.com/jasonmacgowan)) 29 | 30 | ## [v3.2.1](https://github.com/feathersjs/errors/tree/v3.2.1) (2018-01-03) 31 | [Full Changelog](https://github.com/feathersjs/errors/compare/v3.2.0...v3.2.1) 32 | 33 | **Closed issues:** 34 | 35 | - Error handler usage/setup is mis-documented [\#96](https://github.com/feathersjs/errors/issues/96) 36 | 37 | **Merged pull requests:** 38 | 39 | - Update readme to correspond with latest release [\#98](https://github.com/feathersjs/errors/pull/98) ([daffl](https://github.com/daffl)) 40 | - Update semistandard to the latest version 🚀 [\#97](https://github.com/feathersjs/errors/pull/97) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 41 | 42 | ## [v3.2.0](https://github.com/feathersjs/errors/tree/v3.2.0) (2017-11-19) 43 | [Full Changelog](https://github.com/feathersjs/errors/compare/v3.1.0...v3.2.0) 44 | 45 | **Merged pull requests:** 46 | 47 | - Allow ability to log middleware errors [\#95](https://github.com/feathersjs/errors/pull/95) ([daffl](https://github.com/daffl)) 48 | 49 | ## [v3.1.0](https://github.com/feathersjs/errors/tree/v3.1.0) (2017-11-18) 50 | [Full Changelog](https://github.com/feathersjs/errors/compare/v3.0.0...v3.1.0) 51 | 52 | **Closed issues:** 53 | 54 | - feature: allow for mixed files/functions for error-handler options [\#91](https://github.com/feathersjs/errors/issues/91) 55 | 56 | **Merged pull requests:** 57 | 58 | - 91 allow mixed config [\#94](https://github.com/feathersjs/errors/pull/94) ([DesignByOnyx](https://github.com/DesignByOnyx)) 59 | 60 | ## [v3.0.0](https://github.com/feathersjs/errors/tree/v3.0.0) (2017-11-01) 61 | [Full Changelog](https://github.com/feathersjs/errors/compare/v3.0.0-pre.1...v3.0.0) 62 | 63 | **Merged pull requests:** 64 | 65 | - Update to Buzzard infrastructure [\#93](https://github.com/feathersjs/errors/pull/93) ([daffl](https://github.com/daffl)) 66 | 67 | ## [v3.0.0-pre.1](https://github.com/feathersjs/errors/tree/v3.0.0-pre.1) (2017-10-21) 68 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.9.2...v3.0.0-pre.1) 69 | 70 | **Closed issues:** 71 | 72 | - \[Proposal\] use verror [\#88](https://github.com/feathersjs/errors/issues/88) 73 | 74 | **Merged pull requests:** 75 | 76 | - Update to new plugin infrastructure and npm scope [\#92](https://github.com/feathersjs/errors/pull/92) ([daffl](https://github.com/daffl)) 77 | - Update mocha to the latest version 🚀 [\#90](https://github.com/feathersjs/errors/pull/90) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 78 | - Update sinon to the latest version 🚀 [\#89](https://github.com/feathersjs/errors/pull/89) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 79 | 80 | ## [v2.9.2](https://github.com/feathersjs/errors/tree/v2.9.2) (2017-09-05) 81 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.9.1...v2.9.2) 82 | 83 | **Closed issues:** 84 | 85 | - Getting 500 status code when attempting to throw non 500 style errors \(401\) [\#85](https://github.com/feathersjs/errors/issues/85) 86 | 87 | **Merged pull requests:** 88 | 89 | - fix typings [\#87](https://github.com/feathersjs/errors/pull/87) ([j2L4e](https://github.com/j2L4e)) 90 | - Update debug to the latest version 🚀 [\#86](https://github.com/feathersjs/errors/pull/86) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 91 | - Update sinon to the latest version 🚀 [\#84](https://github.com/feathersjs/errors/pull/84) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 92 | 93 | ## [v2.9.1](https://github.com/feathersjs/errors/tree/v2.9.1) (2017-07-21) 94 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.9.0...v2.9.1) 95 | 96 | **Merged pull requests:** 97 | 98 | - Add back default error message [\#83](https://github.com/feathersjs/errors/pull/83) ([daffl](https://github.com/daffl)) 99 | 100 | ## [v2.9.0](https://github.com/feathersjs/errors/tree/v2.9.0) (2017-07-20) 101 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.8.2...v2.9.0) 102 | 103 | **Closed issues:** 104 | 105 | - Wrong stack for errors [\#78](https://github.com/feathersjs/errors/issues/78) 106 | 107 | **Merged pull requests:** 108 | 109 | - Capture proper stack trace and error messages [\#82](https://github.com/feathersjs/errors/pull/82) ([daffl](https://github.com/daffl)) 110 | - Update chai to the latest version 🚀 [\#81](https://github.com/feathersjs/errors/pull/81) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 111 | 112 | ## [v2.8.2](https://github.com/feathersjs/errors/tree/v2.8.2) (2017-07-05) 113 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.8.1...v2.8.2) 114 | 115 | **Merged pull requests:** 116 | 117 | - Fix wildcard import on ES2015+ [\#80](https://github.com/feathersjs/errors/pull/80) ([coreh](https://github.com/coreh)) 118 | - Add more information to error debug [\#79](https://github.com/feathersjs/errors/pull/79) ([kamzil](https://github.com/kamzil)) 119 | 120 | ## [v2.8.1](https://github.com/feathersjs/errors/tree/v2.8.1) (2017-05-30) 121 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.8.0...v2.8.1) 122 | 123 | **Merged pull requests:** 124 | 125 | - Fix errors property being lost when cloning [\#76](https://github.com/feathersjs/errors/pull/76) ([0x6431346e](https://github.com/0x6431346e)) 126 | 127 | ## [v2.8.0](https://github.com/feathersjs/errors/tree/v2.8.0) (2017-05-08) 128 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.7.1...v2.8.0) 129 | 130 | **Closed issues:** 131 | 132 | - Support array objects as data [\#64](https://github.com/feathersjs/errors/issues/64) 133 | 134 | **Merged pull requests:** 135 | 136 | - Allow data to be an array [\#75](https://github.com/feathersjs/errors/pull/75) ([0x6431346e](https://github.com/0x6431346e)) 137 | 138 | ## [v2.7.1](https://github.com/feathersjs/errors/tree/v2.7.1) (2017-04-28) 139 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.7.0...v2.7.1) 140 | 141 | **Closed issues:** 142 | 143 | - Object.setPrototypeOf in IE 10 [\#70](https://github.com/feathersjs/errors/issues/70) 144 | 145 | **Merged pull requests:** 146 | 147 | - Define property toJSON because just assigning it throws an error in N… [\#74](https://github.com/feathersjs/errors/pull/74) ([daffl](https://github.com/daffl)) 148 | 149 | ## [v2.7.0](https://github.com/feathersjs/errors/tree/v2.7.0) (2017-04-25) 150 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.6.3...v2.7.0) 151 | 152 | **Merged pull requests:** 153 | 154 | - Change back to old Error inheritance [\#73](https://github.com/feathersjs/errors/pull/73) ([daffl](https://github.com/daffl)) 155 | - Update semistandard to the latest version 🚀 [\#72](https://github.com/feathersjs/errors/pull/72) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 156 | - Update dependencies to enable Greenkeeper 🌴 [\#71](https://github.com/feathersjs/errors/pull/71) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 157 | 158 | ## [v2.6.3](https://github.com/feathersjs/errors/tree/v2.6.3) (2017-04-08) 159 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.6.2...v2.6.3) 160 | 161 | **Closed issues:** 162 | 163 | - Make options the same as res.format [\#37](https://github.com/feathersjs/errors/issues/37) 164 | 165 | **Merged pull requests:** 166 | 167 | - fix typescript definitions with noImplicitAny [\#69](https://github.com/feathersjs/errors/pull/69) ([JVirant](https://github.com/JVirant)) 168 | 169 | ## [v2.6.2](https://github.com/feathersjs/errors/tree/v2.6.2) (2017-03-16) 170 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.6.1...v2.6.2) 171 | 172 | **Closed issues:** 173 | 174 | - Create a TokenExpired error type [\#53](https://github.com/feathersjs/errors/issues/53) 175 | 176 | **Merged pull requests:** 177 | 178 | - Fix declarations for index.d.ts [\#66](https://github.com/feathersjs/errors/pull/66) ([ghost](https://github.com/ghost)) 179 | 180 | ## [v2.6.1](https://github.com/feathersjs/errors/tree/v2.6.1) (2017-03-06) 181 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.6.0...v2.6.1) 182 | 183 | **Merged pull requests:** 184 | 185 | - fix pull request \#62 [\#63](https://github.com/feathersjs/errors/pull/63) ([superbarne](https://github.com/superbarne)) 186 | 187 | ## [v2.6.0](https://github.com/feathersjs/errors/tree/v2.6.0) (2017-03-04) 188 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.5.0...v2.6.0) 189 | 190 | **Closed issues:** 191 | 192 | - Full Validation Error Object not passed to client promise [\#61](https://github.com/feathersjs/errors/issues/61) 193 | - More HTTP Statuses [\#48](https://github.com/feathersjs/errors/issues/48) 194 | 195 | **Merged pull requests:** 196 | 197 | - add typescript definitions [\#62](https://github.com/feathersjs/errors/pull/62) ([superbarne](https://github.com/superbarne)) 198 | - Fix compile npm task on Windows [\#60](https://github.com/feathersjs/errors/pull/60) ([AbraaoAlves](https://github.com/AbraaoAlves)) 199 | 200 | ## [v2.5.0](https://github.com/feathersjs/errors/tree/v2.5.0) (2016-11-04) 201 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.4.0...v2.5.0) 202 | 203 | **Closed issues:** 204 | 205 | - Possible issue with Node 4 [\#51](https://github.com/feathersjs/errors/issues/51) 206 | - Consider using restify/errors as base [\#31](https://github.com/feathersjs/errors/issues/31) 207 | 208 | **Merged pull requests:** 209 | 210 | - Adding more error types [\#55](https://github.com/feathersjs/errors/pull/55) ([franciscofsales](https://github.com/franciscofsales)) 211 | - 👻😱 Node.js 0.10 is unmaintained 😱👻 [\#54](https://github.com/feathersjs/errors/pull/54) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 212 | - jshint —\> semistandard [\#52](https://github.com/feathersjs/errors/pull/52) ([corymsmith](https://github.com/corymsmith)) 213 | - Update mocha to version 3.0.0 🚀 [\#47](https://github.com/feathersjs/errors/pull/47) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 214 | 215 | ## [v2.4.0](https://github.com/feathersjs/errors/tree/v2.4.0) (2016-07-17) 216 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.3.0...v2.4.0) 217 | 218 | **Merged pull requests:** 219 | 220 | - adding ability to get a feathers error by http status code [\#46](https://github.com/feathersjs/errors/pull/46) ([ekryski](https://github.com/ekryski)) 221 | 222 | ## [v2.3.0](https://github.com/feathersjs/errors/tree/v2.3.0) (2016-07-10) 223 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.2.0...v2.3.0) 224 | 225 | **Closed issues:** 226 | 227 | - Heroku error Reflect.construct [\#44](https://github.com/feathersjs/errors/issues/44) 228 | 229 | **Merged pull requests:** 230 | 231 | - Not found [\#45](https://github.com/feathersjs/errors/pull/45) ([ekryski](https://github.com/ekryski)) 232 | 233 | ## [v2.2.0](https://github.com/feathersjs/errors/tree/v2.2.0) (2016-05-27) 234 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.1.0...v2.2.0) 235 | 236 | **Closed issues:** 237 | 238 | - Can not format error to json [\#35](https://github.com/feathersjs/errors/issues/35) 239 | 240 | **Merged pull requests:** 241 | 242 | - Add an error conversion method [\#43](https://github.com/feathersjs/errors/pull/43) ([daffl](https://github.com/daffl)) 243 | - mocha@2.5.0 breaks build 🚨 [\#42](https://github.com/feathersjs/errors/pull/42) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 244 | - Update babel-plugin-add-module-exports to version 0.2.0 🚀 [\#41](https://github.com/feathersjs/errors/pull/41) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 245 | 246 | ## [v2.1.0](https://github.com/feathersjs/errors/tree/v2.1.0) (2016-04-03) 247 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.0.2...v2.1.0) 248 | 249 | **Closed issues:** 250 | 251 | - Support passing a custom html format function [\#32](https://github.com/feathersjs/errors/issues/32) 252 | 253 | **Merged pull requests:** 254 | 255 | - Custom handlers [\#36](https://github.com/feathersjs/errors/pull/36) ([ekryski](https://github.com/ekryski)) 256 | - Update all dependencies 🌴 [\#34](https://github.com/feathersjs/errors/pull/34) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 257 | 258 | ## [v2.0.2](https://github.com/feathersjs/errors/tree/v2.0.2) (2016-03-23) 259 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.0.1...v2.0.2) 260 | 261 | **Closed issues:** 262 | 263 | - ReferenceError: Reflect is not defined [\#29](https://github.com/feathersjs/errors/issues/29) 264 | - Make error pages opt-in [\#24](https://github.com/feathersjs/errors/issues/24) 265 | 266 | **Merged pull requests:** 267 | 268 | - Update package.json [\#33](https://github.com/feathersjs/errors/pull/33) ([marshallswain](https://github.com/marshallswain)) 269 | - Fixed typo [\#30](https://github.com/feathersjs/errors/pull/30) ([kulakowka](https://github.com/kulakowka)) 270 | 271 | ## [v2.0.1](https://github.com/feathersjs/errors/tree/v2.0.1) (2016-02-24) 272 | [Full Changelog](https://github.com/feathersjs/errors/compare/v2.0.0...v2.0.1) 273 | 274 | **Closed issues:** 275 | 276 | - Error handler is wrapping errors as GeneralErrors [\#27](https://github.com/feathersjs/errors/issues/27) 277 | 278 | **Merged pull requests:** 279 | 280 | - adding an explicit error type [\#28](https://github.com/feathersjs/errors/pull/28) ([ekryski](https://github.com/ekryski)) 281 | 282 | ## [v2.0.0](https://github.com/feathersjs/errors/tree/v2.0.0) (2016-02-24) 283 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.2.4...v2.0.0) 284 | 285 | **Merged pull requests:** 286 | 287 | - move error handler out of index [\#26](https://github.com/feathersjs/errors/pull/26) ([ekryski](https://github.com/ekryski)) 288 | 289 | ## [v1.2.4](https://github.com/feathersjs/errors/tree/v1.2.4) (2016-02-24) 290 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.2.3...v1.2.4) 291 | 292 | ## [v1.2.3](https://github.com/feathersjs/errors/tree/v1.2.3) (2016-02-21) 293 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.2.2...v1.2.3) 294 | 295 | **Merged pull requests:** 296 | 297 | - Adding default error page and make HTML formatting optional [\#25](https://github.com/feathersjs/errors/pull/25) ([daffl](https://github.com/daffl)) 298 | 299 | ## [v1.2.2](https://github.com/feathersjs/errors/tree/v1.2.2) (2016-02-18) 300 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.2.1...v1.2.2) 301 | 302 | **Closed issues:** 303 | 304 | - Add error handler back [\#21](https://github.com/feathersjs/errors/issues/21) 305 | 306 | **Merged pull requests:** 307 | 308 | - Make fully CommonJS compatible and add error middleware tests [\#23](https://github.com/feathersjs/errors/pull/23) ([daffl](https://github.com/daffl)) 309 | 310 | ## [v1.2.1](https://github.com/feathersjs/errors/tree/v1.2.1) (2016-02-16) 311 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.2.0...v1.2.1) 312 | 313 | ## [v1.2.0](https://github.com/feathersjs/errors/tree/v1.2.0) (2016-02-15) 314 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.6...v1.2.0) 315 | 316 | **Closed issues:** 317 | 318 | - Check to make sure that errors propagate via web sockets [\#1](https://github.com/feathersjs/errors/issues/1) 319 | 320 | **Merged pull requests:** 321 | 322 | - adding error handler back [\#22](https://github.com/feathersjs/errors/pull/22) ([ekryski](https://github.com/ekryski)) 323 | 324 | ## [v1.1.6](https://github.com/feathersjs/errors/tree/v1.1.6) (2016-01-12) 325 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.5...v1.1.6) 326 | 327 | **Closed issues:** 328 | 329 | - stacktraces are incorrect when used in an ES6 app [\#20](https://github.com/feathersjs/errors/issues/20) 330 | - We shouldn't mutate the error object passed in. [\#19](https://github.com/feathersjs/errors/issues/19) 331 | - only one instance of babel-polyfill is allowed [\#17](https://github.com/feathersjs/errors/issues/17) 332 | 333 | ## [v1.1.5](https://github.com/feathersjs/errors/tree/v1.1.5) (2015-12-18) 334 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.4...v1.1.5) 335 | 336 | ## [v1.1.4](https://github.com/feathersjs/errors/tree/v1.1.4) (2015-12-15) 337 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.3...v1.1.4) 338 | 339 | **Closed issues:** 340 | 341 | - no method 'setPrototypeOf' in Node 0.10 [\#16](https://github.com/feathersjs/errors/issues/16) 342 | 343 | ## [v1.1.3](https://github.com/feathersjs/errors/tree/v1.1.3) (2015-12-15) 344 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.2...v1.1.3) 345 | 346 | ## [v1.1.2](https://github.com/feathersjs/errors/tree/v1.1.2) (2015-12-15) 347 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.1...v1.1.2) 348 | 349 | **Closed issues:** 350 | 351 | - Passing errors as second argument [\#9](https://github.com/feathersjs/errors/issues/9) 352 | 353 | ## [v1.1.1](https://github.com/feathersjs/errors/tree/v1.1.1) (2015-12-14) 354 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.1.0...v1.1.1) 355 | 356 | **Closed issues:** 357 | 358 | - Subclassing Errors using babel don't behave as expected [\#14](https://github.com/feathersjs/errors/issues/14) 359 | 360 | **Merged pull requests:** 361 | 362 | - Es6 class fix [\#15](https://github.com/feathersjs/errors/pull/15) ([ekryski](https://github.com/ekryski)) 363 | 364 | ## [v1.1.0](https://github.com/feathersjs/errors/tree/v1.1.0) (2015-12-12) 365 | [Full Changelog](https://github.com/feathersjs/errors/compare/v1.0.0...v1.1.0) 366 | 367 | ## [v1.0.0](https://github.com/feathersjs/errors/tree/v1.0.0) (2015-12-12) 368 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.2.5...v1.0.0) 369 | 370 | **Closed issues:** 371 | 372 | - Convert to ES6 [\#12](https://github.com/feathersjs/errors/issues/12) 373 | - Drop the error handlers: Breaking Change [\#11](https://github.com/feathersjs/errors/issues/11) 374 | - Remove Lodash dependency [\#10](https://github.com/feathersjs/errors/issues/10) 375 | - Logging only unhandled errors [\#8](https://github.com/feathersjs/errors/issues/8) 376 | 377 | **Merged pull requests:** 378 | 379 | - complete rewrite. Closes \#11 and \#12. [\#13](https://github.com/feathersjs/errors/pull/13) ([ekryski](https://github.com/ekryski)) 380 | 381 | ## [0.2.5](https://github.com/feathersjs/errors/tree/0.2.5) (2015-02-05) 382 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.2.4...0.2.5) 383 | 384 | ## [0.2.4](https://github.com/feathersjs/errors/tree/0.2.4) (2015-02-05) 385 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.2.3...0.2.4) 386 | 387 | ## [0.2.3](https://github.com/feathersjs/errors/tree/0.2.3) (2015-01-29) 388 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.2.2...0.2.3) 389 | 390 | ## [0.2.2](https://github.com/feathersjs/errors/tree/0.2.2) (2015-01-29) 391 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.2.1...0.2.2) 392 | 393 | ## [0.2.1](https://github.com/feathersjs/errors/tree/0.2.1) (2014-09-03) 394 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.2.0...0.2.1) 395 | 396 | ## [0.2.0](https://github.com/feathersjs/errors/tree/0.2.0) (2014-07-17) 397 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.7...0.2.0) 398 | 399 | **Implemented enhancements:** 400 | 401 | - Handle error objects with an 'errors' object [\#5](https://github.com/feathersjs/errors/issues/5) 402 | 403 | ## [0.1.7](https://github.com/feathersjs/errors/tree/0.1.7) (2014-07-06) 404 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.6...0.1.7) 405 | 406 | ## [0.1.6](https://github.com/feathersjs/errors/tree/0.1.6) (2014-07-05) 407 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.5...0.1.6) 408 | 409 | ## [0.1.5](https://github.com/feathersjs/errors/tree/0.1.5) (2014-06-13) 410 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.4...0.1.5) 411 | 412 | ## [0.1.4](https://github.com/feathersjs/errors/tree/0.1.4) (2014-06-13) 413 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.3...0.1.4) 414 | 415 | **Closed issues:** 416 | 417 | - Move errors into core [\#2](https://github.com/feathersjs/errors/issues/2) 418 | 419 | **Merged pull requests:** 420 | 421 | - Core compatible [\#4](https://github.com/feathersjs/errors/pull/4) ([ekryski](https://github.com/ekryski)) 422 | 423 | ## [0.1.3](https://github.com/feathersjs/errors/tree/0.1.3) (2014-06-09) 424 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.2...0.1.3) 425 | 426 | **Merged pull requests:** 427 | 428 | - Adding a default error page [\#3](https://github.com/feathersjs/errors/pull/3) ([ekryski](https://github.com/ekryski)) 429 | 430 | ## [0.1.2](https://github.com/feathersjs/errors/tree/0.1.2) (2014-06-05) 431 | [Full Changelog](https://github.com/feathersjs/errors/compare/0.1.1...0.1.2) 432 | 433 | ## [0.1.1](https://github.com/feathersjs/errors/tree/0.1.1) (2014-06-04) 434 | [Full Changelog](https://github.com/feathersjs/errors/compare/v0.1.0...0.1.1) 435 | 436 | ## [v0.1.0](https://github.com/feathersjs/errors/tree/v0.1.0) (2014-06-04) 437 | 438 | 439 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Feathers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @feathersjs/errors 2 | 3 | > __Important:__ The code for this module has been moved into the main Feathers repository at [feathersjs/feathers](https://github.com/feathersjs/feathers) ([package direct link](https://github.com/feathersjs/feathers/tree/master/packages/errors)). Please open issues and pull requests there. No changes in your existing Feathers applications are necessary. 4 | 5 | [![Build Status](https://travis-ci.org/feathersjs/errors.png?branch=master)](https://travis-ci.org/feathersjs/errors) 6 | 7 | > Common error types for feathers apps 8 | 9 | ## Installation 10 | 11 | ``` 12 | npm install @feathersjs/errors --save 13 | ``` 14 | 15 | Quick usage: 16 | 17 | ```js 18 | const errors = require('@feathersjs/errors'); 19 | 20 | // If you were to create an error yourself. 21 | const notFound = new errors.NotFound('User does not exist'); 22 | 23 | // You can wrap existing errors 24 | const existing = new errors.GeneralError(new Error('I exist')); 25 | 26 | // You can also pass additional data 27 | const data = new errors.BadRequest('Invalid email', { 28 | email: 'sergey@google.com' 29 | }); 30 | 31 | // You can also pass additional data without a message 32 | const dataWithoutMessage = new errors.BadRequest({ 33 | email: 'sergey@google.com' 34 | }); 35 | 36 | // If you need to pass multiple errors 37 | const validationErrors = new errors.BadRequest('Invalid Parameters', { 38 | errors: { email: 'Email already taken' } 39 | }); 40 | 41 | // You can also omit the error message and we'll put in a default one for you 42 | const validationErrors = new errors.BadRequest({ 43 | errors: { 44 | email: 'Invalid Email' 45 | } 46 | }); 47 | ``` 48 | 49 | ## Documentation 50 | 51 | Please refer to the [@feathersjs/errors API documentation](https://docs.feathersjs.com/api/errors.html) for more details. 52 | 53 | ## License 54 | 55 | Copyright (c) 2018 Feathers Contributors 56 | 57 | Licensed under the [MIT license](LICENSE). 58 | -------------------------------------------------------------------------------- /handler.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/error-handler'); 2 | -------------------------------------------------------------------------------- /lib/error-handler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const errors = require('./index'); 3 | 4 | const defaults = { 5 | public: path.resolve(__dirname, 'public'), 6 | logger: console 7 | }; 8 | const defaultHtmlError = path.resolve(defaults.public, 'default.html'); 9 | 10 | module.exports = function (options = {}) { 11 | options = Object.assign({}, defaults, options); 12 | 13 | if (typeof options.html === 'undefined') { 14 | options.html = { 15 | 401: path.resolve(options.public, '401.html'), 16 | 404: path.resolve(options.public, '404.html'), 17 | default: defaultHtmlError 18 | }; 19 | } 20 | 21 | if (typeof options.json === 'undefined') { 22 | options.json = {}; 23 | } 24 | 25 | return function (error, req, res, next) { 26 | // Log the error if it didn't come from a service method call 27 | if (options.logger && typeof options.logger.error === 'function' && !res.hook) { 28 | options.logger.error(error); 29 | } 30 | 31 | if (error.type !== 'FeathersError') { 32 | let oldError = error; 33 | error = new errors.GeneralError(oldError.message, { 34 | errors: oldError.errors 35 | }); 36 | 37 | if (oldError.stack) { 38 | error.stack = oldError.stack; 39 | } 40 | } 41 | 42 | error.code = !isNaN(parseInt(error.code, 10)) ? parseInt(error.code, 10) : 500; 43 | const formatter = {}; 44 | 45 | // If the developer passed a custom function for ALL html errors 46 | if (typeof options.html === 'function') { 47 | formatter['text/html'] = options.html; 48 | } else { 49 | let file = options.html[error.code]; 50 | if (!file) { 51 | file = options.html.default || defaultHtmlError; 52 | } 53 | // If the developer passed a custom function for individual html errors 54 | if (typeof file === 'function') { 55 | formatter['text/html'] = file; 56 | } else { 57 | formatter['text/html'] = function () { 58 | res.set('Content-Type', 'text/html'); 59 | res.sendFile(file); 60 | }; 61 | } 62 | } 63 | 64 | // If the developer passed a custom function for ALL json errors 65 | if (typeof options.json === 'function') { 66 | formatter['application/json'] = options.json; 67 | } else { 68 | let handler = options.json[error.code] || options.json.default; 69 | // If the developer passed a custom function for individual json errors 70 | if (typeof handler === 'function') { 71 | formatter['application/json'] = handler; 72 | } else { 73 | // Don't show stack trace if it is a 404 error 74 | if (error.code === 404) { 75 | error.stack = null; 76 | } 77 | 78 | formatter['application/json'] = function () { 79 | let output = Object.assign({}, error.toJSON()); 80 | 81 | if (process.env.NODE_ENV === 'production') { 82 | delete output.stack; 83 | } 84 | 85 | res.set('Content-Type', 'application/json'); 86 | res.json(output); 87 | }; 88 | } 89 | } 90 | 91 | res.status(error.code); 92 | 93 | const contentType = req.headers['content-type'] || ''; 94 | const accepts = req.headers.accept || ''; 95 | 96 | // by default just send back json 97 | if (contentType.indexOf('json') !== -1 || accepts.indexOf('json') !== -1) { 98 | formatter['application/json'](error, req, res, next); 99 | } else if (options.html && (contentType.indexOf('html') !== -1 || accepts.indexOf('html') !== -1)) { 100 | formatter['text/html'](error, req, res, next); 101 | } else { 102 | // TODO (EK): Maybe just return plain text 103 | formatter['application/json'](error, req, res, next); 104 | } 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('@feathersjs/errors'); 2 | 3 | function FeathersError (msg, name, code, className, data) { 4 | msg = msg || 'Error'; 5 | 6 | let errors; 7 | let message; 8 | let newData; 9 | 10 | if (msg instanceof Error) { 11 | message = msg.message || 'Error'; 12 | 13 | // NOTE (EK): This is typically to handle validation errors 14 | if (msg.errors) { 15 | errors = msg.errors; 16 | } 17 | } else if (typeof msg === 'object') { // Support plain old objects 18 | message = msg.message || 'Error'; 19 | data = msg; 20 | } else { // message is just a string 21 | message = msg; 22 | } 23 | 24 | if (data) { 25 | // NOTE(EK): To make sure that we are not messing 26 | // with immutable data, just make a copy. 27 | // https://github.com/feathersjs/errors/issues/19 28 | newData = JSON.parse(JSON.stringify(data)); 29 | 30 | if (newData.errors) { 31 | errors = newData.errors; 32 | delete newData.errors; 33 | } else if (data.errors) { 34 | // The errors property from data could be 35 | // stripped away while cloning resulting newData not to have it 36 | // For example: when cloning arrays this property 37 | errors = JSON.parse(JSON.stringify(data.errors)); 38 | } 39 | } 40 | 41 | // NOTE (EK): Babel doesn't support this so 42 | // we have to pass in the class name manually. 43 | // this.name = this.constructor.name; 44 | this.type = 'FeathersError'; 45 | this.name = name; 46 | this.message = message; 47 | this.code = code; 48 | this.className = className; 49 | this.data = newData; 50 | this.errors = errors || {}; 51 | 52 | debug(`${this.name}(${this.code}): ${this.message}`); 53 | debug(this.errors); 54 | 55 | if (Error.captureStackTrace) { 56 | Error.captureStackTrace(this, FeathersError); 57 | } else { 58 | this.stack = (new Error()).stack; 59 | } 60 | } 61 | 62 | function inheritsFrom (Child, Parent) { 63 | Child.prototype = Object.create(Parent.prototype); 64 | Child.prototype.constructor = Child; 65 | } 66 | 67 | inheritsFrom(FeathersError, Error); 68 | 69 | // NOTE (EK): A little hack to get around `message` not 70 | // being included in the default toJSON call. 71 | Object.defineProperty(FeathersError.prototype, 'toJSON', { 72 | value: function () { 73 | return { 74 | name: this.name, 75 | message: this.message, 76 | code: this.code, 77 | className: this.className, 78 | data: this.data, 79 | errors: this.errors 80 | }; 81 | } 82 | }); 83 | 84 | // 400 - Bad Request 85 | function BadRequest (message, data) { 86 | FeathersError.call(this, message, 'BadRequest', 400, 'bad-request', data); 87 | } 88 | 89 | inheritsFrom(BadRequest, FeathersError); 90 | 91 | // 401 - Not Authenticated 92 | function NotAuthenticated (message, data) { 93 | FeathersError.call(this, message, 'NotAuthenticated', 401, 'not-authenticated', data); 94 | } 95 | 96 | inheritsFrom(NotAuthenticated, FeathersError); 97 | 98 | // 402 - Payment Error 99 | function PaymentError (message, data) { 100 | FeathersError.call(this, message, 'PaymentError', 402, 'payment-error', data); 101 | } 102 | 103 | inheritsFrom(PaymentError, FeathersError); 104 | 105 | // 403 - Forbidden 106 | function Forbidden (message, data) { 107 | FeathersError.call(this, message, 'Forbidden', 403, 'forbidden', data); 108 | } 109 | 110 | inheritsFrom(Forbidden, FeathersError); 111 | 112 | // 404 - Not Found 113 | function NotFound (message, data) { 114 | FeathersError.call(this, message, 'NotFound', 404, 'not-found', data); 115 | } 116 | 117 | inheritsFrom(NotFound, FeathersError); 118 | 119 | // 405 - Method Not Allowed 120 | function MethodNotAllowed (message, data) { 121 | FeathersError.call(this, message, 'MethodNotAllowed', 405, 'method-not-allowed', data); 122 | } 123 | 124 | inheritsFrom(MethodNotAllowed, FeathersError); 125 | 126 | // 406 - Not Acceptable 127 | function NotAcceptable (message, data) { 128 | FeathersError.call(this, message, 'NotAcceptable', 406, 'not-acceptable', data); 129 | } 130 | 131 | inheritsFrom(NotAcceptable, FeathersError); 132 | 133 | // 408 - Timeout 134 | function Timeout (message, data) { 135 | FeathersError.call(this, message, 'Timeout', 408, 'timeout', data); 136 | } 137 | 138 | inheritsFrom(Timeout, FeathersError); 139 | 140 | // 409 - Conflict 141 | function Conflict (message, data) { 142 | FeathersError.call(this, message, 'Conflict', 409, 'conflict', data); 143 | } 144 | 145 | inheritsFrom(Conflict, FeathersError); 146 | 147 | // 411 - Length Required 148 | function LengthRequired (message, data) { 149 | FeathersError.call(this, message, 'LengthRequired', 411, 'length-required', data); 150 | } 151 | 152 | inheritsFrom(LengthRequired, FeathersError); 153 | 154 | // 422 Unprocessable 155 | function Unprocessable (message, data) { 156 | FeathersError.call(this, message, 'Unprocessable', 422, 'unprocessable', data); 157 | } 158 | 159 | inheritsFrom(Unprocessable, FeathersError); 160 | 161 | // 429 Too Many Requests 162 | function TooManyRequests (message, data) { 163 | FeathersError.call(this, message, 'TooManyRequests', 429, 'too-many-requests', data); 164 | } 165 | 166 | inheritsFrom(TooManyRequests, FeathersError); 167 | 168 | // 500 - General Error 169 | function GeneralError (message, data) { 170 | FeathersError.call(this, message, 'GeneralError', 500, 'general-error', data); 171 | } 172 | 173 | inheritsFrom(GeneralError, FeathersError); 174 | 175 | // 501 - Not Implemented 176 | function NotImplemented (message, data) { 177 | FeathersError.call(this, message, 'NotImplemented', 501, 'not-implemented', data); 178 | } 179 | 180 | inheritsFrom(NotImplemented, FeathersError); 181 | 182 | // 502 - Bad Gateway 183 | function BadGateway (message, data) { 184 | FeathersError.call(this, message, 'BadGateway', 502, 'bad-gateway', data); 185 | } 186 | 187 | inheritsFrom(BadGateway, FeathersError); 188 | 189 | // 503 - Unavailable 190 | function Unavailable (message, data) { 191 | FeathersError.call(this, message, 'Unavailable', 503, 'unavailable', data); 192 | } 193 | 194 | inheritsFrom(Unavailable, FeathersError); 195 | 196 | const errors = { 197 | FeathersError, 198 | BadRequest, 199 | NotAuthenticated, 200 | PaymentError, 201 | Forbidden, 202 | NotFound, 203 | MethodNotAllowed, 204 | NotAcceptable, 205 | Timeout, 206 | Conflict, 207 | LengthRequired, 208 | Unprocessable, 209 | TooManyRequests, 210 | GeneralError, 211 | NotImplemented, 212 | BadGateway, 213 | Unavailable, 214 | 400: BadRequest, 215 | 401: NotAuthenticated, 216 | 402: PaymentError, 217 | 403: Forbidden, 218 | 404: NotFound, 219 | 405: MethodNotAllowed, 220 | 406: NotAcceptable, 221 | 408: Timeout, 222 | 409: Conflict, 223 | 411: LengthRequired, 224 | 422: Unprocessable, 225 | 429: TooManyRequests, 226 | 500: GeneralError, 227 | 501: NotImplemented, 228 | 502: BadGateway, 229 | 503: Unavailable 230 | }; 231 | 232 | function convert (error) { 233 | if (!error) { 234 | return error; 235 | } 236 | 237 | const FeathersError = errors[error.name]; 238 | const result = FeathersError 239 | ? new FeathersError(error.message, error.data) 240 | : new Error(error.message || error); 241 | 242 | if (typeof error === 'object') { 243 | Object.assign(result, error); 244 | } 245 | 246 | return result; 247 | } 248 | 249 | module.exports = Object.assign({ convert }, errors); 250 | -------------------------------------------------------------------------------- /lib/not-found-handler.js: -------------------------------------------------------------------------------- 1 | const errors = require('./index'); 2 | 3 | module.exports = function ({ verbose = false } = {}) { 4 | return function (req, res, next) { 5 | const { url } = req; 6 | const message = `Page not found${verbose ? ': ' + url : ''}`; 7 | next(new errors.NotFound(message, { url })); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /lib/public/401.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Not Authorized 4 | 56 | 57 | 58 |
59 |

401

60 |

Not Authorized

61 | 62 | 65 |
66 | 67 | -------------------------------------------------------------------------------- /lib/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Page Not Found 4 | 56 | 57 | 58 |
59 |

404

60 |

Page Not Found

61 | 64 |
65 | 66 | -------------------------------------------------------------------------------- /lib/public/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Internal Server Error 4 | 56 | 57 | 58 |
59 |

Oh no!

60 |

Something went wrong

61 | 64 |
65 | 66 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive test/ 2 | -------------------------------------------------------------------------------- /not-found.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/not-found-handler'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@feathersjs/errors", 3 | "description": "Common error types for feathers apps", 4 | "version": "3.3.0", 5 | "homepage": "https://github.com/feathersjs/errors", 6 | "main": "lib/index", 7 | "keywords": [ 8 | "feathers", 9 | "feathers-plugin" 10 | ], 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/feathersjs/errors.git" 15 | }, 16 | "author": { 17 | "name": "Feathers contributors", 18 | "email": "hello@feathersjs.com", 19 | "url": "https://feathersjs.com" 20 | }, 21 | "contributors": [], 22 | "bugs": { 23 | "url": "https://github.com/feathersjs/errors/issues" 24 | }, 25 | "engines": { 26 | "node": ">= 6" 27 | }, 28 | "scripts": { 29 | "publish": "git push origin --tags && npm run changelog && git push origin", 30 | "release:pre": "npm version prerelease && npm publish --access public --tag pre", 31 | "release:patch": "npm version patch && npm publish --access public", 32 | "release:minor": "npm version minor && npm publish --access public", 33 | "release:major": "npm version major && npm publish --access public", 34 | "changelog": "github_changelog_generator && git add CHANGELOG.md && git commit -am \"Updating changelog\"", 35 | "lint": "semistandard --fix", 36 | "mocha": "mocha --opts mocha.opts", 37 | "test": "npm run lint && npm run coverage", 38 | "coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts" 39 | }, 40 | "semistandard": { 41 | "env": [ 42 | "mocha" 43 | ] 44 | }, 45 | "directories": { 46 | "lib": "lib" 47 | }, 48 | "dependencies": { 49 | "debug": "^3.1.0" 50 | }, 51 | "devDependencies": { 52 | "chai": "^4.1.0", 53 | "feathers": "^2.0.0", 54 | "istanbul": "^1.1.0-alpha.1", 55 | "mocha": "^5.0.0", 56 | "request": "^2.69.0", 57 | "semistandard": "^12.0.0", 58 | "shx": "^0.3.0", 59 | "sinon": "^6.0.0", 60 | "sinon-chai": "^3.0.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/error-handler.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable handle-callback-err */ 2 | /* eslint-disable no-unused-expressions */ 3 | const feathers = require('feathers'); 4 | 5 | const chai = require('chai'); 6 | const sinon = require('sinon'); 7 | const sinonChai = require('sinon-chai'); 8 | const request = require('request'); 9 | const fs = require('fs'); 10 | const { join } = require('path'); 11 | 12 | const errors = require('../lib'); 13 | const handler = require('../lib/error-handler'); 14 | 15 | chai.use(sinonChai); 16 | 17 | const { expect } = chai; 18 | const content = 'Error'; 19 | 20 | let htmlHandler = sinon.spy(function (error, req, res, next) { 21 | res.send(content); 22 | }); 23 | 24 | const jsonHandler = sinon.spy(function (error, req, res, next) { 25 | res.json(error); 26 | }); 27 | 28 | describe('error-handler', () => { 29 | it('is CommonJS compatible', () => { 30 | expect(typeof require('../lib/error-handler')).to.equal('function'); 31 | }); 32 | 33 | it('can be required at the root', () => { 34 | expect(typeof require('../handler')).to.equal('function'); 35 | }); 36 | 37 | it('is import compatible', () => { 38 | expect(typeof handler).to.equal('function'); 39 | }); 40 | 41 | describe('supports catch-all custom handlers', function () { 42 | let currentError; 43 | 44 | before(function () { 45 | this.app = feathers() 46 | .get('/error', function (req, res, next) { 47 | next(new Error('Something went wrong')); 48 | }) 49 | .use(handler({ 50 | html: htmlHandler, 51 | json: jsonHandler, 52 | logger: { 53 | error (e) { 54 | currentError = e; 55 | } 56 | } 57 | })); 58 | 59 | this.server = this.app.listen(5050); 60 | }); 61 | 62 | after(function (done) { 63 | this.server.close(done); 64 | }); 65 | 66 | describe('HTML handler', () => { 67 | const options = { 68 | url: 'http://localhost:5050/error', 69 | headers: { 70 | 'Content-Type': 'text/html', 71 | 'Accept': 'text/html' 72 | } 73 | }; 74 | 75 | it('is called', done => { 76 | request(options, (error, res, body) => { 77 | expect(htmlHandler).to.be.called; // eslint-disable-line 78 | done(); 79 | }); 80 | }); 81 | 82 | it('logs the error', done => { 83 | request(options, (error, res, body) => { 84 | expect(currentError.message).to.equal('Something went wrong'); 85 | done(); 86 | }); 87 | }); 88 | 89 | it('can send a custom response', done => { 90 | request(options, (error, res, body) => { 91 | expect(body).to.equal(content); 92 | done(); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('JSON handler', () => { 98 | const options = { 99 | url: 'http://localhost:5050/error', 100 | headers: { 101 | 'Content-Type': 'application/json', 102 | 'Accept': 'application/json' 103 | } 104 | }; 105 | 106 | it('is called', done => { 107 | request(options, (error, res, body) => { 108 | expect(jsonHandler).to.be.called; 109 | done(); 110 | }); 111 | }); 112 | 113 | it('can send a custom response', done => { 114 | const expected = JSON.stringify({ 115 | name: 'GeneralError', 116 | message: 'Something went wrong', 117 | code: 500, 118 | className: 'general-error', 119 | data: {}, 120 | errors: {} 121 | }); 122 | request(options, (error, res, body) => { 123 | expect(body).to.deep.equal(expected); 124 | done(); 125 | }); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('supports error-code specific custom handlers', () => { 131 | describe('HTML handler', () => { 132 | const req = { 133 | headers: { 'content-type': 'text/html' } 134 | }; 135 | const makeRes = (errCode, props) => { 136 | return Object.assign({ 137 | set () {}, 138 | status (code) { 139 | expect(code).to.equal(errCode); 140 | } 141 | }, props); 142 | }; 143 | 144 | it('if the value is a string, calls res.sendFile', done => { 145 | const err = new errors.NotAuthenticated(); 146 | const middleware = handler({ 147 | html: { 401: 'path/to/401.html' } 148 | }); 149 | const res = makeRes(401, { 150 | sendFile (f) { 151 | expect(f).to.equal('path/to/401.html'); 152 | done(); 153 | } 154 | }); 155 | middleware(err, req, res); 156 | }); 157 | 158 | it('if the value is a function, calls as middleware ', done => { 159 | const err = new errors.PaymentError(); 160 | const res = makeRes(402); 161 | const middleware = handler({ 162 | html: { 402: (_err, _req, _res) => { 163 | expect(_err).to.equal(err); 164 | expect(_req).to.equal(req); 165 | expect(_res).to.equal(res); 166 | done(); 167 | }} 168 | }); 169 | middleware(err, req, res); 170 | }); 171 | 172 | it('falls back to default if error code config is available', done => { 173 | const err = new errors.NotAcceptable(); 174 | const res = makeRes(406); 175 | const middleware = handler({ 176 | html: { default: (_err, _req, _res) => { 177 | expect(_err).to.equal(err); 178 | expect(_req).to.equal(req); 179 | expect(_res).to.equal(res); 180 | done(); 181 | }} 182 | }); 183 | middleware(err, req, res); 184 | }); 185 | }); 186 | 187 | describe('JSON handler', () => { 188 | const req = { 189 | headers: { 'content-type': 'application/json' } 190 | }; 191 | const makeRes = (errCode, props) => { 192 | return Object.assign({ 193 | set () {}, 194 | status (code) { 195 | expect(code).to.equal(errCode); 196 | } 197 | }, props); 198 | }; 199 | 200 | it('calls res.json by default', done => { 201 | const err = new errors.NotAuthenticated(); 202 | const middleware = handler({ 203 | json: {} 204 | }); 205 | const res = makeRes(401, { 206 | json (obj) { 207 | expect(obj).to.deep.equal(err.toJSON()); 208 | done(); 209 | } 210 | }); 211 | middleware(err, req, res); 212 | }); 213 | 214 | it('if the value is a function, calls as middleware ', done => { 215 | const err = new errors.PaymentError(); 216 | const res = makeRes(402); 217 | const middleware = handler({ 218 | json: { 402: (_err, _req, _res) => { 219 | expect(_err).to.equal(err); 220 | expect(_req).to.equal(req); 221 | expect(_res).to.equal(res); 222 | done(); 223 | }} 224 | }); 225 | middleware(err, req, res); 226 | }); 227 | 228 | it('falls back to default if error code config is available', done => { 229 | const err = new errors.NotAcceptable(); 230 | const res = makeRes(406); 231 | const middleware = handler({ 232 | json: { default: (_err, _req, _res) => { 233 | expect(_err).to.equal(err); 234 | expect(_req).to.equal(req); 235 | expect(_res).to.equal(res); 236 | done(); 237 | }} 238 | }); 239 | middleware(err, req, res); 240 | }); 241 | }); 242 | }); 243 | 244 | describe('use as app error handler', function () { 245 | before(function () { 246 | this.app = feathers() 247 | .get('/error', function (req, res, next) { 248 | next(new Error('Something went wrong')); 249 | }) 250 | .get('/string-error', function (req, res, next) { 251 | const e = new Error('Something was not found'); 252 | e.code = '404'; 253 | 254 | next(e); 255 | }) 256 | .get('/bad-request', function (req, res, next) { 257 | next(new errors.BadRequest({ 258 | message: 'Invalid Password', 259 | errors: [{ 260 | path: 'password', 261 | value: null, 262 | message: `'password' cannot be 'null'` 263 | }] 264 | })); 265 | }) 266 | .use(function (req, res, next) { 267 | next(new errors.NotFound('File not found')); 268 | }) 269 | .use(handler()); 270 | 271 | this.server = this.app.listen(5050); 272 | }); 273 | 274 | after(function (done) { 275 | this.server.close(done); 276 | }); 277 | 278 | describe('converts an non-feathers error', () => { 279 | it('is an instance of GeneralError', done => { 280 | request({ 281 | url: 'http://localhost:5050/error', 282 | json: true 283 | }, (error, res, body) => { 284 | expect(res.statusCode).to.equal(500); 285 | expect(body).to.deep.equal({ 286 | name: 'GeneralError', 287 | message: 'Something went wrong', 288 | code: 500, 289 | className: 'general-error', 290 | data: {}, 291 | errors: {} 292 | }); 293 | done(); 294 | }); 295 | }); 296 | 297 | it.skip('still has a stack trace', () => { 298 | expect(handler).to.equal('function'); 299 | }); 300 | }); 301 | 302 | describe('text/html format', () => { 303 | it('serves a 404.html', done => { 304 | fs.readFile(join(__dirname, '..', 'lib', 'public', '404.html'), function (err, html) { 305 | request({ 306 | url: 'http://localhost:5050/path/to/nowhere', 307 | headers: { 308 | 'Content-Type': 'text/html', 309 | 'Accept': 'text/html' 310 | } 311 | }, (error, res, body) => { 312 | expect(res.statusCode).to.equal(404); 313 | expect(html.toString()).to.equal(body); 314 | done(); 315 | }); 316 | }); 317 | }); 318 | 319 | it('serves a 500.html', done => { 320 | fs.readFile(join(__dirname, '..', 'lib', 'public', 'default.html'), function (err, html) { 321 | request({ 322 | url: 'http://localhost:5050/error', 323 | headers: { 324 | 'Content-Type': 'text/html', 325 | 'Accept': 'text/html' 326 | } 327 | }, (error, res, body) => { 328 | expect(res.statusCode).to.equal(500); 329 | expect(html.toString()).to.equal(body); 330 | done(); 331 | }); 332 | }); 333 | }); 334 | 335 | it('returns html when Content-Type header is set', done => { 336 | fs.readFile(join(__dirname, '..', 'lib', 'public', '404.html'), function (err, html) { 337 | request({ 338 | url: 'http://localhost:5050/path/to/nowhere', 339 | headers: { 340 | 'Content-Type': 'text/html' 341 | } 342 | }, (error, res, body) => { 343 | expect(res.statusCode).to.equal(404); 344 | expect(html.toString()).to.equal(body); 345 | done(); 346 | }); 347 | }); 348 | }); 349 | 350 | it('returns html when Accept header is set', done => { 351 | fs.readFile(join(__dirname, '..', 'lib', 'public', '404.html'), function (err, html) { 352 | request({ 353 | url: 'http://localhost:5050/path/to/nowhere', 354 | headers: { 355 | 'Accept': 'text/html' 356 | } 357 | }, (error, res, body) => { 358 | expect(res.statusCode).to.equal(404); 359 | expect(html.toString()).to.equal(body); 360 | done(); 361 | }); 362 | }); 363 | }); 364 | }); 365 | 366 | describe('application/json format', () => { 367 | it('500', done => { 368 | request({ 369 | url: 'http://localhost:5050/error', 370 | headers: { 371 | 'Content-Type': 'application/json', 372 | 'Accept': 'application/json' 373 | }, 374 | json: true 375 | }, (error, res, body) => { 376 | expect(res.statusCode).to.equal(500); 377 | expect(body).to.deep.equal({ 378 | name: 'GeneralError', 379 | message: 'Something went wrong', 380 | code: 500, 381 | className: 'general-error', 382 | data: {}, 383 | errors: {} 384 | }); 385 | done(); 386 | }); 387 | }); 388 | 389 | it('404', done => { 390 | request({ 391 | url: 'http://localhost:5050/path/to/nowhere', 392 | headers: { 393 | 'Content-Type': 'application/json', 394 | 'Accept': 'application/json' 395 | }, 396 | json: true 397 | }, (error, res, body) => { 398 | expect(res.statusCode).to.equal(404); 399 | expect(body).to.deep.equal({ name: 'NotFound', 400 | message: 'File not found', 401 | code: 404, 402 | className: 'not-found', 403 | errors: {} 404 | }); 405 | done(); 406 | }); 407 | }); 408 | 409 | it('400', done => { 410 | request({ 411 | url: 'http://localhost:5050/bad-request', 412 | headers: { 413 | 'Content-Type': 'application/json', 414 | 'Accept': 'application/json' 415 | }, 416 | json: true 417 | }, (error, res, body) => { 418 | expect(res.statusCode).to.equal(400); 419 | expect(body).to.deep.equal({ name: 'BadRequest', 420 | message: 'Invalid Password', 421 | code: 400, 422 | className: 'bad-request', 423 | data: { 424 | message: 'Invalid Password' 425 | }, 426 | errors: [{ 427 | path: 'password', 428 | value: null, 429 | message: `'password' cannot be 'null'` 430 | }] 431 | }); 432 | done(); 433 | }); 434 | }); 435 | 436 | it('returns JSON when only Content-Type header is set', done => { 437 | request({ 438 | url: 'http://localhost:5050/bad-request', 439 | headers: { 440 | 'Content-Type': 'application/json' 441 | }, 442 | json: true 443 | }, (error, res, body) => { 444 | expect(res.statusCode).to.equal(400); 445 | expect(body).to.deep.equal({ name: 'BadRequest', 446 | message: 'Invalid Password', 447 | code: 400, 448 | className: 'bad-request', 449 | data: { 450 | message: 'Invalid Password' 451 | }, 452 | errors: [{ 453 | path: 'password', 454 | value: null, 455 | message: `'password' cannot be 'null'` 456 | }] 457 | }); 458 | done(); 459 | }); 460 | }); 461 | 462 | it('returns JSON when only Accept header is set', done => { 463 | request({ 464 | url: 'http://localhost:5050/bad-request', 465 | headers: { 466 | 'Accept': 'application/json' 467 | }, 468 | json: true 469 | }, (error, res, body) => { 470 | expect(res.statusCode).to.equal(400); 471 | expect(body).to.deep.equal({ name: 'BadRequest', 472 | message: 'Invalid Password', 473 | code: 400, 474 | className: 'bad-request', 475 | data: { 476 | message: 'Invalid Password' 477 | }, 478 | errors: [{ 479 | path: 'password', 480 | value: null, 481 | message: `'password' cannot be 'null'` 482 | }] 483 | }); 484 | done(); 485 | }); 486 | }); 487 | }); 488 | 489 | it('returns JSON by default', done => { 490 | request('http://localhost:5050/bad-request', (error, res, body) => { 491 | const expected = JSON.stringify({ 492 | name: 'BadRequest', 493 | message: 'Invalid Password', 494 | code: 400, 495 | className: 'bad-request', 496 | data: { 497 | message: 'Invalid Password' 498 | }, 499 | errors: [{ 500 | path: 'password', 501 | value: null, 502 | message: `'password' cannot be 'null'` 503 | }] 504 | }); 505 | 506 | expect(res.statusCode).to.equal(400); 507 | expect(body).to.deep.equal(expected); 508 | done(); 509 | }); 510 | }); 511 | }); 512 | }); 513 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const errors = require('../lib'); 4 | const { convert } = errors; 5 | 6 | describe('@feathersjs/errors', () => { 7 | it('is CommonJS compatible', () => { 8 | assert.equal(typeof require('../lib'), 'object'); 9 | assert.equal(typeof require('../lib').FeathersError, 'function'); 10 | }); 11 | 12 | describe('errors.convert', () => { 13 | it('converts objects to feathers errors', () => { 14 | const error = convert({ 15 | name: 'BadRequest', 16 | message: 'Hi', 17 | expando: 'Me' 18 | }); 19 | 20 | assert.ok(error instanceof errors.BadRequest); 21 | assert.equal(error.message, 'Hi'); 22 | assert.equal(error.expando, 'Me'); 23 | }); 24 | 25 | it('converts other object to error', () => { 26 | let error = convert({ 27 | message: 'Something went wrong' 28 | }); 29 | 30 | assert.ok(error instanceof Error); 31 | assert.equal(error.message, 'Something went wrong'); 32 | 33 | error = convert('Something went wrong'); 34 | 35 | assert.ok(error instanceof Error); 36 | assert.equal(error.message, 'Something went wrong'); 37 | }); 38 | 39 | it('converts nothing', () => 40 | assert.equal(convert(null), null) 41 | ); 42 | }); 43 | 44 | describe('error types', () => { 45 | it('Bad Request', () => { 46 | assert.notEqual(typeof errors.BadRequest, 'undefined', 'has BadRequest'); 47 | }); 48 | 49 | it('Not Authenticated', () => { 50 | assert.notEqual(typeof errors.NotAuthenticated, 'undefined', 'has NotAuthenticated'); 51 | }); 52 | 53 | it('Payment Error', () => { 54 | assert.notEqual(typeof errors.PaymentError, 'undefined', 'has PaymentError'); 55 | }); 56 | 57 | it('Forbidden', () => { 58 | assert.notEqual(typeof errors.Forbidden, 'undefined', 'has Forbidden'); 59 | }); 60 | 61 | it('Not Found', () => { 62 | assert.notEqual(typeof errors.NotFound, 'undefined', 'has NotFound'); 63 | }); 64 | 65 | it('Method Not Allowed', () => { 66 | assert.notEqual(typeof errors.MethodNotAllowed, 'undefined', 'has MethodNotAllowed'); 67 | }); 68 | 69 | it('Not Acceptable', () => { 70 | assert.notEqual(typeof errors.NotAcceptable, 'undefined', 'has NotAcceptable'); 71 | }); 72 | 73 | it('Timeout', () => { 74 | assert.notEqual(typeof errors.Timeout, 'undefined', 'has Timeout'); 75 | }); 76 | 77 | it('Conflict', () => { 78 | assert.notEqual(typeof errors.Conflict, 'undefined', 'has Conflict'); 79 | }); 80 | 81 | it('Length Required', () => { 82 | assert.notEqual(typeof errors.LengthRequired, 'undefined', 'has LengthRequired'); 83 | }); 84 | 85 | it('Unprocessable', () => { 86 | assert.notEqual(typeof errors.Unprocessable, 'undefined', 'has Unprocessable'); 87 | }); 88 | 89 | it('Too Many Requests', () => { 90 | assert.notEqual(typeof errors.TooManyRequests, 'undefined', 'has TooManyRequests'); 91 | }); 92 | 93 | it('General Error', () => { 94 | assert.notEqual(typeof errors.GeneralError, 'undefined', 'has GeneralError'); 95 | }); 96 | 97 | it('Not Implemented', () => { 98 | assert.notEqual(typeof errors.NotImplemented, 'undefined', 'has NotImplemented'); 99 | }); 100 | 101 | it('Bad Gateway', () => { 102 | assert.notEqual(typeof errors.BadGateway, 'undefined', 'has BadGateway'); 103 | }); 104 | 105 | it('Unavailable', () => { 106 | assert.notEqual(typeof errors.Unavailable, 'undefined', 'has Unavailable'); 107 | }); 108 | 109 | it('400', () => { 110 | assert.notEqual(typeof errors[400], 'undefined', 'has BadRequest alias'); 111 | }); 112 | 113 | it('401', () => { 114 | assert.notEqual(typeof errors[401], 'undefined', 'has NotAuthenticated alias'); 115 | }); 116 | 117 | it('402', () => { 118 | assert.notEqual(typeof errors[402], 'undefined', 'has PaymentError alias'); 119 | }); 120 | 121 | it('403', () => { 122 | assert.notEqual(typeof errors[403], 'undefined', 'has Forbidden alias'); 123 | }); 124 | 125 | it('404', () => { 126 | assert.notEqual(typeof errors[404], 'undefined', 'has NotFound alias'); 127 | }); 128 | 129 | it('405', () => { 130 | assert.notEqual(typeof errors[405], 'undefined', 'has MethodNotAllowed alias'); 131 | }); 132 | 133 | it('406', () => { 134 | assert.notEqual(typeof errors[406], 'undefined', 'has NotAcceptable alias'); 135 | }); 136 | 137 | it('408', () => { 138 | assert.notEqual(typeof errors[408], 'undefined', 'has Timeout alias'); 139 | }); 140 | 141 | it('409', () => { 142 | assert.notEqual(typeof errors[409], 'undefined', 'has Conflict alias'); 143 | }); 144 | 145 | it('411', () => { 146 | assert.notEqual(typeof errors[411], 'undefined', 'has LengthRequired alias'); 147 | }); 148 | 149 | it('422', () => { 150 | assert.notEqual(typeof errors[422], 'undefined', 'has Unprocessable alias'); 151 | }); 152 | 153 | it('429', () => { 154 | assert.notEqual(typeof errors[429], 'undefined', 'has TooManyRequests alias'); 155 | }); 156 | 157 | it('500', () => { 158 | assert.notEqual(typeof errors[500], 'undefined', 'has GeneralError alias'); 159 | }); 160 | 161 | it('501', () => { 162 | assert.notEqual(typeof errors[501], 'undefined', 'has NotImplemented alias'); 163 | }); 164 | 165 | it('502', () => { 166 | assert.notEqual(typeof errors[502], 'undefined', 'has BadGateway alias'); 167 | }); 168 | 169 | it('503', () => { 170 | assert.notEqual(typeof errors[503], 'undefined', 'has Unavailable alias'); 171 | }); 172 | 173 | it('instantiates every error', () => { 174 | Object.keys(errors).forEach(name => { 175 | if (name === 'convert') { 176 | return; 177 | } 178 | 179 | const E = errors[name]; 180 | 181 | if (E) { 182 | new E('Something went wrong'); // eslint-disable-line no-new 183 | } 184 | }); 185 | }); 186 | }); 187 | 188 | describe('inheritance', () => { 189 | it('instanceof differentiates between error types', () => { 190 | const error = new errors.MethodNotAllowed(); 191 | assert.ok(!(error instanceof errors.BadRequest)); 192 | }); 193 | 194 | it('follows the prototypical inheritance chain', () => { 195 | const error = new errors.MethodNotAllowed(); 196 | assert.ok(error instanceof Error); 197 | assert.ok(error instanceof errors.FeathersError); 198 | }); 199 | 200 | it('has the correct constructors', () => { 201 | const error = new errors.NotFound(); 202 | assert.ok(error.constructor === errors.NotFound); 203 | assert.ok(error.constructor.name === 'NotFound'); 204 | }); 205 | }); 206 | 207 | describe('successful error creation', () => { 208 | describe('without custom message', () => { 209 | it('default error', () => { 210 | var error = new errors.GeneralError(); 211 | assert.equal(error.code, 500); 212 | assert.equal(error.className, 'general-error'); 213 | assert.equal(error.message, 'Error'); 214 | assert.notEqual(error.stack, undefined); 215 | assert.equal(error instanceof errors.GeneralError, true); 216 | assert.equal(error instanceof errors.FeathersError, true); 217 | }); 218 | 219 | it('can wrap an existing error', () => { 220 | var error = new errors.BadRequest(new Error()); 221 | assert.equal(error.code, 400); 222 | assert.equal(error.message, 'Error'); 223 | }); 224 | 225 | it('with multiple errors', () => { 226 | var data = { 227 | errors: { 228 | email: 'Email Taken', 229 | password: 'Invalid Password' 230 | }, 231 | foo: 'bar' 232 | }; 233 | 234 | var error = new errors.BadRequest(data); 235 | assert.equal(error.code, 400); 236 | assert.equal(error.message, 'Error'); 237 | assert.deepEqual(error.errors, {email: 'Email Taken', password: 'Invalid Password'}); 238 | assert.deepEqual(error.data, {foo: 'bar'}); 239 | }); 240 | 241 | it('with data', () => { 242 | var data = { 243 | email: 'Email Taken', 244 | password: 'Invalid Password' 245 | }; 246 | 247 | var error = new errors.GeneralError(data); 248 | assert.equal(error.code, 500); 249 | assert.equal(error.message, 'Error'); 250 | assert.deepEqual(error.data, data); 251 | }); 252 | }); 253 | 254 | describe('with custom message', () => { 255 | it('contains our message', () => { 256 | var error = new errors.BadRequest('Invalid Password'); 257 | assert.equal(error.code, 400); 258 | assert.equal(error.message, 'Invalid Password'); 259 | }); 260 | 261 | it('can wrap an existing error', () => { 262 | var error = new errors.BadRequest(new Error('Invalid Password')); 263 | assert.equal(error.code, 400); 264 | assert.equal(error.message, 'Invalid Password'); 265 | }); 266 | 267 | it('with data', () => { 268 | var data = { 269 | email: 'Email Taken', 270 | password: 'Invalid Password' 271 | }; 272 | 273 | var error = new errors.GeneralError('Custom Error', data); 274 | assert.equal(error.code, 500); 275 | assert.equal(error.message, 'Custom Error'); 276 | assert.deepEqual(error.data, data); 277 | }); 278 | 279 | it('with multiple errors', () => { 280 | var data = { 281 | errors: { 282 | email: 'Email Taken', 283 | password: 'Invalid Password' 284 | }, 285 | foo: 'bar' 286 | }; 287 | 288 | var error = new errors.BadRequest(data); 289 | assert.equal(error.code, 400); 290 | assert.equal(error.message, 'Error'); 291 | assert.deepEqual(error.errors, {email: 'Email Taken', password: 'Invalid Password'}); 292 | assert.deepEqual(error.data, {foo: 'bar'}); 293 | }); 294 | }); 295 | 296 | it('can return JSON', () => { 297 | var data = { 298 | errors: { 299 | email: 'Email Taken', 300 | password: 'Invalid Password' 301 | }, 302 | foo: 'bar' 303 | }; 304 | 305 | var expected = '{"name":"GeneralError","message":"Custom Error","code":500,"className":"general-error","data":{"foo":"bar"},"errors":{"email":"Email Taken","password":"Invalid Password"}}'; 306 | 307 | var error = new errors.GeneralError('Custom Error', data); 308 | assert.equal(JSON.stringify(error), expected); 309 | }); 310 | 311 | it('can handle immutable data', () => { 312 | var data = { 313 | errors: { 314 | email: 'Email Taken', 315 | password: 'Invalid Password' 316 | }, 317 | foo: 'bar' 318 | }; 319 | 320 | var error = new errors.GeneralError('Custom Error', Object.freeze(data)); 321 | assert.equal(error.data.errors, undefined); 322 | assert.deepEqual(error.data, {foo: 'bar'}); 323 | }); 324 | 325 | it('allows arrays as data', () => { 326 | var data = [ 327 | { 328 | hello: 'world' 329 | } 330 | ]; 331 | data.errors = 'Invalid input'; 332 | 333 | var error = new errors.GeneralError('Custom Error', data); 334 | assert.equal(error.data.errors, undefined); 335 | assert.ok(Array.isArray(error.data)); 336 | assert.deepEqual(error.data, [{hello: 'world'}]); 337 | assert.equal(error.errors, 'Invalid input'); 338 | }); 339 | 340 | it('has proper stack trace (#78)', () => { 341 | try { 342 | throw new errors.NotFound('Not the error you are looking for'); 343 | } catch (e) { 344 | const text = 'NotFound: Not the error you are looking for'; 345 | 346 | assert.equal(e.stack.indexOf(text), 0); 347 | 348 | assert.ok(e.stack.indexOf('index.test.js') !== -1); 349 | 350 | const oldCST = Error.captureStackTrace; 351 | 352 | delete Error.captureStackTrace; 353 | 354 | try { 355 | throw new errors.NotFound('Not the error you are looking for'); 356 | } catch (e) { 357 | assert.ok(e); 358 | Error.captureStackTrace = oldCST; 359 | } 360 | } 361 | }); 362 | }); 363 | }); 364 | -------------------------------------------------------------------------------- /test/not-found-handler.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const sinonChai = require('sinon-chai'); 3 | 4 | const errors = require('../lib'); 5 | 6 | const handler = require('../lib/not-found-handler'); 7 | 8 | const { expect } = chai; 9 | 10 | chai.use(sinonChai); 11 | 12 | describe('not-found-handler', () => { 13 | it('is CommonJS compatible', () => { 14 | expect(typeof require('../lib/not-found-handler')).to.equal('function'); 15 | }); 16 | 17 | it('can be required at the root', () => { 18 | expect(typeof require('../not-found')).to.equal('function'); 19 | }); 20 | 21 | it('is import compatible', () => { 22 | expect(typeof handler).to.equal('function'); 23 | }); 24 | 25 | it('returns NotFound error', done => { 26 | handler()({ 27 | url: 'some/where', 28 | headers: {} 29 | }, {}, function (error) { 30 | expect(error instanceof errors.NotFound).to.equal(true); 31 | expect(error.message).to.equal('Page not found'); 32 | expect(error.data).to.deep.equal({ 33 | url: 'some/where' 34 | }); 35 | done(); 36 | }); 37 | }); 38 | 39 | it('returns NotFound error with URL when verbose', done => { 40 | handler({ verbose: true })({ 41 | url: 'some/where', 42 | headers: {} 43 | }, {}, function (error) { 44 | expect(error instanceof errors.NotFound).to.equal(true); 45 | expect(error.message).to.equal('Page not found: some/where'); 46 | expect(error.data).to.deep.equal({ 47 | url: 'some/where' 48 | }); 49 | done(); 50 | }); 51 | }); 52 | }); 53 | --------------------------------------------------------------------------------