├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CHANGES.md ├── LICENSE ├── Makefile ├── README.md ├── lib ├── baseClasses │ ├── HttpError.js │ └── RestError.js ├── helpers.js ├── httpErrors.js ├── index.js ├── makeConstructor.js ├── restErrors.js └── serializer.js ├── package.json ├── test ├── .eslintrc └── index.js └── tools └── githooks └── pre-push /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // eslint: recommended automatically enables most/all rules from the 3 | // possible errors section and more: 4 | // http://eslint.org/docs/rules/#possible-errors 5 | "extends": "eslint:recommended", 6 | "env": { 7 | "browser": false, 8 | "node": true, 9 | "es6": false 10 | }, 11 | "rules": { 12 | // possible errors 13 | "no-cond-assign": [ 2 ], 14 | "no-constant-condition": [ 2 ], 15 | "no-control-regex": [ 2 ], 16 | "no-debugger": [ 2 ], 17 | "no-dupe-args": [ 2 ], 18 | "no-dupe-keys": [ 2 ], 19 | "no-duplicate-case": [ 2 ], 20 | "no-empty": [ 2 ], 21 | "no-empty-character-class": [ 2 ], 22 | "no-ex-assign": [ 2 ], 23 | "no-extra-boolean-cast": [ 2 ], 24 | "no-extra-semi": [ 2 ], 25 | "no-func-assign": [ 2 ], 26 | // this is for variable hoisting, not necessary if we use block scoped declarations 27 | // "no-inner-declarations": [ 2, "both" ], 28 | "no-invalid-regexp": [ 2 ], 29 | "no-irregular-whitespace": [ 2 ], 30 | "no-reserved-keys": [ 0 ], 31 | "no-regex-spaces": [ 2 ], 32 | "no-sparse-arrays": [ 2 ], 33 | "no-unreachable": [ 2 ], 34 | "no-unsafe-negation": [ 2 ], 35 | "use-isnan": [ 2 ], 36 | "valid-jsdoc": [ 2, { 37 | "requireReturnDescription": false 38 | }], 39 | "valid-typeof": [ 2 ], 40 | 41 | // best practices 42 | "array-callback-return": [ 2 ], 43 | "block-scoped-var": [ 2 ], 44 | "class-methods-use-this": [ 2 ], 45 | "complexity": [ 1 ], 46 | "consistent-return": [ 2 ], 47 | "curly": [ 2 ], 48 | "default-case": [ 2 ], 49 | "dot-notation": [ 2, { "allowKeywords": true } ], 50 | "eqeqeq": [ 2 ], 51 | "guard-for-in": [ 2 ], 52 | "no-alert": [ 2 ], 53 | "no-caller": [ 2 ], 54 | "no-case-declarations": [ 2 ], 55 | "no-div-regex": [ 2 ], 56 | "no-empty-function": [ 2 ], 57 | "no-empty-pattern": [ 2 ], 58 | "no-eq-null": [ 2 ], 59 | "no-eval": [ 2 ], 60 | "no-extend-native": [ 2 ], 61 | "no-extra-bind": [ 2 ], 62 | "no-extra-label": [ 2 ], 63 | "no-fallthrough": [ 2 ], 64 | "no-floating-decimal": [ 2 ], 65 | "no-global-assign": [ 2 ], 66 | "no-implicit-coercion": [ 2 ], 67 | "no-implied-eval": [ 2 ], 68 | "no-iterator": [ 2 ], 69 | "no-labels": [ 2 ], 70 | "no-lone-blocks": [ 2 ], 71 | "no-loop-func": [ 2 ], 72 | "no-magic-numbers": [ 0 ], 73 | "no-multi-spaces": [ 0 ], 74 | "no-new": [ 2 ], 75 | "no-new-func": [ 2 ], 76 | "no-new-wrappers": [ 2 ], 77 | "no-octal": [ 2 ], 78 | "no-octal-escape": [ 2 ], 79 | "no-param-reassign": [ 2 ], 80 | "no-proto": [ 2 ], 81 | "no-redeclare": [ 2 ], 82 | "no-return-assign": [ 2 ], 83 | "no-script-url": [ 2 ], 84 | "no-self-assign": [ 2 ], 85 | "no-self-compare": [ 2 ], 86 | "no-sequences": [ 2 ], 87 | "no-throw-literal": [ 2 ], 88 | "no-unmodified-loop-condition": [ 2 ], 89 | "no-unused-expressions": [ 2 ], 90 | "no-unused-labels": [ 2 ], 91 | "no-useless-call": [ 2 ], 92 | "no-useless-concat": [ 2 ], 93 | "no-void": [ 2 ], 94 | "no-warning-comments": [ 1 ], 95 | "no-with": [ 2 ], 96 | "wrap-iife": [ 2 ], 97 | "yoda": [ 2, "never" ], 98 | 99 | // strict mode 100 | "strict": [ 2, "global" ], 101 | 102 | // variables 103 | "no-catch-shadow": [ 2 ], 104 | "no-delete-var": [ 2 ], 105 | "no-shadow": [ 2 ], 106 | "no-shadow-restricted-names": [ 2 ], 107 | "no-undef": [ 2 ], 108 | "no-undef-init": [ 2 ], 109 | "no-unused-vars": [ 2, { "vars": "all", "args": "none" } ], 110 | "no-use-before-define": [ 2, "nofunc" ], 111 | 112 | // node.js 113 | "callback-return": [ 2, [ "callback", "cb", "cb1", "cb2", "cb3", "next", "innerCb", "done" ]], 114 | "global-require": [ 2 ], 115 | "handle-callback-err": [ 2, "^.*(e|E)rr" ], 116 | "no-mixed-requires": [ 2 ], 117 | "no-new-require": [ 2 ], 118 | "no-path-concat": [ 2 ], 119 | "no-process-exit": [ 2 ], 120 | 121 | // stylistic 122 | "array-bracket-newline": [ 2, { "multiline": true }], 123 | "array-bracket-spacing": [ 2, "always", { 124 | "singleValue": true, 125 | "objectsInArrays": false, 126 | "arraysInArrays": false 127 | }], 128 | "block-spacing": [ 2, "always" ], 129 | "camelcase": [ 2, { 130 | "properties": "always" 131 | }], 132 | "comma-dangle": [ 2, "never" ], 133 | "comma-spacing": [ 2, { "before": false, "after": true }], 134 | "comma-style": [ 2, "last" ], 135 | "computed-property-spacing": [ 2, "never" ], 136 | "consistent-this": [ 2, "self" ], 137 | "func-call-spacing": [ 2, "never" ], 138 | "func-style": [ 2, "declaration" ], 139 | "function-paren-newline": [ 2, "consistent" ], 140 | "indent": [ 2, 4, { 141 | "CallExpression": { "arguments": "off" } 142 | }], 143 | "key-spacing": [ 2, { 144 | "beforeColon": false, 145 | "afterColon": true 146 | }], 147 | "keyword-spacing": [ 2, { 148 | "before": true, 149 | "after": true 150 | }], 151 | "lines-between-class-members": [ 2, "always" ], 152 | "max-len": [ 2, { 153 | "code": 80 154 | }], 155 | "multiline-ternary": [ 2, "always-multiline" ], 156 | "new-cap": [ 2, { 157 | "newIsCap": true, 158 | "properties": true 159 | }], 160 | "new-parens": [ 2 ], 161 | "no-array-constructor": [ 2 ], 162 | "no-mixed-operators": [ 2 ], 163 | "no-nested-ternary": [ 2 ], 164 | "no-new-object": [ 2 ], 165 | "no-trailing-spaces": [ 2 ], 166 | "no-unneeded-ternary": [ 2 ], 167 | "no-whitespace-before-property": [ 2 ], 168 | "object-curly-newline": [ 2, { "consistent": true }], 169 | "object-curly-spacing": [ 2, "always", { 170 | "arraysInObjects": false, 171 | "objectsInObjects": false 172 | }], 173 | "one-var-declaration-per-line": [ 2 ], 174 | "operator-linebreak": [ 2, "after" ], 175 | "padding-line-between-statements": [ 2, 176 | { 177 | "blankLine": "always", 178 | "prev": "directive", 179 | "next": "*" 180 | }, 181 | { 182 | "blankLine": "any", 183 | "prev": "directive", 184 | "next": "directive" 185 | }, 186 | { 187 | "blankLine": "always", 188 | "prev": "*", 189 | "next": "cjs-export" 190 | }, 191 | { 192 | "blankLine": "always", 193 | "prev": "*", 194 | "next": "export" 195 | }, 196 | { 197 | "blankLine": "always", 198 | "prev": "block", 199 | "next": "*" 200 | } 201 | ], 202 | "quotes": [ 2, "single", { "avoidEscape": true }], 203 | "require-jsdoc": [ 2, { 204 | "require": { 205 | "FunctionDeclaration": true, 206 | "MethodDefinition": true, 207 | "ClassDeclaration": true, 208 | "ArrowFunctionExpression": false, 209 | "FunctionExpression": false 210 | } 211 | }], 212 | "semi": [ 2, "always" ], 213 | "space-before-blocks": [ 2, "always" ], 214 | "space-before-function-paren": [ 2, "never" ], 215 | "space-in-parens": [ 2, "never" ], 216 | "space-infix-ops": [ 2 ], 217 | "space-unary-ops": [ 2, { "words": true, "nonwords": false }], 218 | "spaced-comment": [ 2, "always", { 219 | "exceptions": ["-"] 220 | }], 221 | "switch-colon-spacing": [ 2, { "after": true, "before": false }], 222 | "template-tag-spacing": [ 2, "never" ], 223 | 224 | /* 225 | // es6 226 | "arrow-body-style": [ 2, "always" ], 227 | "arrow-parens": [ 2, "always" ], 228 | "arrow-spacing": [ 2 ], 229 | "constructor-super": [ 2 ], 230 | "generator-star-spacing": [ 2, { "before": false, "after": true }], 231 | "no-class-assign": [ 2 ], 232 | "no-dupe-class-members": [ 2 ], 233 | "no-duplicate-imports": [ 2 ], 234 | "no-new-symbol": [ 2 ], 235 | "no-const-assign": [ 2 ], 236 | "no-useless-computed-key": [ 2 ], 237 | "no-useless-constructor": [ 2 ], 238 | "no-useless-rename": [ 2 ], 239 | "no-var": [ 2 ], 240 | "prefer-const": [ 2 ], 241 | "sort-imports": [ 2 ] 242 | */ 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # OS generated files # 4 | ###################### 5 | .DS_Store 6 | .DS_Store? 7 | ._* 8 | .Spotlight-V100 9 | .Trashes 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | /node_modules/ 14 | npm-debug.log 15 | package-lock.json 16 | yarn.lock 17 | 18 | # build task results for ci 19 | coverage/ 20 | .nyc_output/ 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | npm: false 3 | language: node_js 4 | node_js: 5 | - '8' 6 | - '10' 7 | - '12' 8 | - "lts/*" # Active LTS release 9 | - "node" # Latest stable release 10 | after_success: 11 | - make report-coverage 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ### 8.0.2 (2019-12-13) 3 | 4 | 5 | 6 | ### 8.0.1 (2019-07-26) 7 | 8 | 9 | 10 | ## 8.0.0 (2019-05-06) 11 | 12 | 13 | #### Bug Fixes 14 | 15 | * topLevelFields should not serialize known VError proto fields (#91) ([3060c9c6](https://github.com/restify/errors.git/commit/3060c9c6)) 16 | * remove duplication of serialized Error properties (#90) ([1da7c76f](https://github.com/restify/errors.git/commit/1da7c76f)) 17 | 18 | 19 | #### Features 20 | 21 | * **HttpError:** use @netflix/nerror instead of verror ([a37b7f00](https://github.com/restify/errors.git/commit/a37b7f00)) 22 | 23 | 24 | #### Breaking Changes 25 | 26 | * drop 4.x and 6.x Node support 27 | 28 | ([56daf0b1](https://github.com/restify/errors.git/commit/56daf0b1)) 29 | 30 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## 7.0.0 4 | 5 | - BREAKING: omit node domains from serializer 6 | 7 | ## 6.1.1 8 | 9 | - FIX: don't serialize arbitrary top level fields that are fields known to 10 | VError classes. 11 | 12 | ## 6.1.0 13 | 14 | - NEW: support serialization of arbitrary top level fields in log serializer. 15 | this is opt in via the new serializer factory. 16 | - FIX: remove duplication of Error properties for VError objects 17 | 18 | ## 6.0.0 19 | 20 | - BREAKING: All Error constructors now mirror VError constructor APIs. Re-export 21 | all VError static methods on restify-errors exports. 22 | 23 | ## 5.0.0 24 | 25 | - BREAKING: (arguably a fix) Custom Error constructors now return the custom 26 | error name when serialized via `toJSON()` or `toString()` 27 | 28 | ## 4.3.0 29 | 30 | - NEW: The bunyan serializer now handles regular VError objects using the new 31 | `info` property. It also supports VError's MultiError. 32 | 33 | ## 4.2.3 34 | 35 | - FIX: for errors with a cause chain, `toString()` now leverages VError to get 36 | the full error message when serializing to JSON. 37 | 38 | ## 4.2.2 39 | 40 | - FIX: remove `toString()` method that was overriding VError's existing 41 | `toString()`. This was causing truncated error messages. 42 | 43 | ## 4.2.1 44 | 45 | - FIX: Fix issue where `e.cause` was assumed to be a function, causing 46 | serializer to fail. 47 | 48 | ## 4.2.0 49 | 50 | - FIX: Use safe-json-stringify module to to do JSON serialization of objects 51 | with circular objects. 52 | 53 | ## 4.1.0 54 | 55 | - NEW: add bunyan serializer for handling the new `context` property on errors 56 | created by restify-errors. 57 | 58 | 59 | ## 4.0.0 60 | 61 | - NEW: Error constructor now takes `options.context`, which is a bucket of 62 | random properties that are saved to the Error object being created. 63 | - NEW: All Errors now have `toString()` and `toJSON()` methods. These are 64 | overridable via `makeConstructor()`. 65 | - BREAKING: `code` and `restCode` properties were normalized across all 66 | classes. `code` property now has a value of 'Error' for HttpError and 67 | RestError. Any subclass will have the name of the error, minus 'Error', 68 | i.e., GatewayTimeoutError has a code of GatewayTimeout. All code and 69 | restCode properties are now overridable. 70 | 71 | ## 3.1.0 72 | - rev dependencies 73 | 74 | ## 3.0.0 75 | - restify/node-restify#844 Errors now live in its own repo and npm module. 76 | - all error constructors now support [VError](https://github.com/davepacheco/node-verror) constructor args. 77 | - Support subclass and custom error types via `makeConstructor()` 78 | - Support creating errors using HTTP status codes via `makeErrFromCode()`. Was 79 | previously a private method used to create internal error types, this is now 80 | exposed publicly for user consumption. 81 | 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Directories 3 | # 4 | ROOT_SLASH := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) 5 | ROOT := $(patsubst %/,%,$(ROOT_SLASH)) 6 | TEST := $(ROOT)/test 7 | TOOLS := $(ROOT)/tools 8 | GITHOOKS_SRC := $(TOOLS)/githooks 9 | GITHOOKS_DEST := $(ROOT)/.git/hooks 10 | 11 | 12 | # 13 | # Generated Directories 14 | # 15 | NODE_MODULES := $(ROOT)/node_modules 16 | NODE_BIN := $(NODE_MODULES)/.bin 17 | COVERAGE := $(ROOT)/coverage 18 | 19 | 20 | # 21 | # Tools and binaries 22 | # 23 | YARN := yarn 24 | NPM := npm 25 | COVERALLS := $(NODE_BIN)/coveralls 26 | ESLINT := $(NODE_BIN)/eslint 27 | ISTANBUL := $(NODE_BIN)/nyc 28 | MOCHA := $(NODE_BIN)/mocha 29 | _MOCHA := $(NODE_BIN)/_mocha 30 | UNLEASH := $(NODE_BIN)/unleash 31 | CONVENTIONAL_RECOMMENDED_BUMP := $(NODE_BIN)/conventional-recommended-bump 32 | 33 | 34 | # 35 | # Files 36 | # 37 | LCOV := $(ROOT)/coverage/lcov.info 38 | PACKAGE_JSON := $(ROOT)/package.json 39 | YARN_LOCK := $(ROOT)/yarn.lock 40 | GITHOOKS := $(wildcard $(GITHOOKS_SRC)/*) 41 | ALL_FILES := $(shell find $(ROOT) \ 42 | -not \( -path $(NODE_MODULES) -prune \) \ 43 | -not \( -path $(COVERAGE) -prune \) \ 44 | -name '*.js' -type f) 45 | 46 | 47 | # 48 | # Targets 49 | # 50 | 51 | .PHONY: help 52 | help: 53 | @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) \ 54 | | sort | awk 'BEGIN {FS = ":.*?## "}; \ 55 | {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 56 | 57 | 58 | .PHONY: all 59 | all: node_modules lint test clean-coverage 60 | 61 | 62 | $(YARN_LOCK): $(PACKAGE_JSON) 63 | @$(YARN) 64 | 65 | 66 | $(NODE_MODULES): $(PACKAGE_JSON) 67 | @$(YARN) 68 | @touch $(NODE_MODULES) 69 | 70 | 71 | .PHONY: githooks 72 | githooks: ## Install githooks 73 | @ln -s $(GIT_HOOK_SRC) $(GIT_HOOK_DEST) 74 | 75 | 76 | .PHONY: release-dry 77 | release-dry: $(NODE_MODULES) ## Dry run of `release` target 78 | $(UNLEASH) -d --type=$(shell $(CONVENTIONAL_RECOMMENDED_BUMP) -p angular) 79 | 80 | 81 | .PHONY: release 82 | release: $(NODE_MODULES) ## Versions, tags, and updates changelog based on commit messages 83 | $(UNLEASH) --type=$(shell $(CONVENTIONAL_RECOMMENDED_BUMP) -p angular) --no-publish 84 | $(NPM) publish 85 | 86 | 87 | .PHONY: lint 88 | lint: $(NODE_MODULES) ## Run lint and style checks 89 | @$(ESLINT) $(ALL_FILES) 90 | 91 | 92 | .PHONY: prepush 93 | prepush: $(NODE_MODULES) lint test ## Run all required tasks for a git push 94 | 95 | 96 | .PHONY: test 97 | test: $(NODE_MODULES) ## Run unit tests 98 | @$(MOCHA) -R spec --full-trace 99 | 100 | 101 | .PHONY: coverage 102 | coverage: $(NODE_MODULES) clean-coverage ## Generate test coverage 103 | @$(ISTANBUL) --report lcovonly $(_MOCHA) -R spec 104 | 105 | 106 | .PHONY: report-coverage 107 | report-coverage: $(NODE_MODULES) coverage ## Report test coverage to Coveralls 108 | $(ISTANBUL) report --reporter=text-lcov | $(COVERALLS) 109 | 110 | 111 | .PHONY: clean-coverage 112 | clean-coverage: 113 | @rm -rf $(COVERAGE) 114 | 115 | 116 | .PHONY: clean 117 | clean: clean-coverage ## Clean all generated directories 118 | @rm -rf $(NODE_MODULES) 119 | 120 | 121 | # 122 | ## Debug -- print out a a variable via `make print-FOO` 123 | # 124 | print-% : ; @echo $* = $($*) 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # restify-errors 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/restify-errors.svg)](https://npmjs.org/package/restify-errors) 4 | [![Build Status](https://travis-ci.org/restify/errors.svg?branch=master)](https://travis-ci.org/restify/errors) 5 | [![Coverage Status](https://coveralls.io/repos/restify/errors/badge.svg?branch=master)](https://coveralls.io/r/restify/errors?branch=master) 6 | [![Dependency Status](https://david-dm.org/restify/errors.svg)](https://david-dm.org/restify/errors) 7 | [![devDependency Status](https://david-dm.org/restify/errors/dev-status.svg)](https://david-dm.org/restify/errors#info=devDependencies) 8 | 9 | > A collection of HTTP and REST Error constructors. 10 | 11 | This module ships with a set of constructors that can be used to new up Error 12 | objects with default status codes. 13 | 14 | The module ships with the following HttpErrors: 15 | 16 | * 400 BadRequestError 17 | * 401 UnauthorizedError 18 | * 402 PaymentRequiredError 19 | * 403 ForbiddenError 20 | * 404 NotFoundError 21 | * 405 MethodNotAllowedError 22 | * 406 NotAcceptableError 23 | * 407 ProxyAuthenticationRequiredError 24 | * 408 RequestTimeoutError 25 | * 409 ConflictError 26 | * 410 GoneError 27 | * 411 LengthRequiredError 28 | * 412 PreconditionFailedError 29 | * 413 RequestEntityTooLargeError 30 | * 414 RequesturiTooLargeError 31 | * 415 UnsupportedMediaTypeError 32 | * 416 RangeNotSatisfiableError (For Node >= 4 & iojs >= 3) 33 | * 416 RequestedRangeNotSatisfiableError (For Node 0.x & iojs < 3) 34 | * 417 ExpectationFailedError 35 | * 418 ImATeapotError 36 | * 422 UnprocessableEntityError 37 | * 423 LockedError 38 | * 424 FailedDependencyError 39 | * 425 UnorderedCollectionError 40 | * 426 UpgradeRequiredError 41 | * 428 PreconditionRequiredError 42 | * 429 TooManyRequestsError 43 | * 431 RequestHeaderFieldsTooLargeError 44 | * 500 InternalServerError 45 | * 501 NotImplementedError 46 | * 502 BadGatewayError 47 | * 503 ServiceUnavailableError 48 | * 504 GatewayTimeoutError 49 | * 505 HttpVersionNotSupportedError 50 | * 506 VariantAlsoNegotiatesError 51 | * 507 InsufficientStorageError 52 | * 509 BandwidthLimitExceededError 53 | * 510 NotExtendedError 54 | * 511 NetworkAuthenticationRequiredError 55 | 56 | and the following RestErrors: 57 | 58 | * 400 BadDigestError 59 | * 405 BadMethodError 60 | * 500 InternalError 61 | * 409 InvalidArgumentError 62 | * 400 InvalidContentError 63 | * 401 InvalidCredentialsError 64 | * 400 InvalidHeaderError 65 | * 400 InvalidVersionError 66 | * 409 MissingParameterError 67 | * 403 NotAuthorizedError 68 | * 412 PreconditionFailedError 69 | * 400 RequestExpiredError 70 | * 429 RequestThrottledError 71 | * 404 ResourceNotFoundError 72 | * 406 WrongAcceptError 73 | 74 | Some of the status codes overlap, since applications can choose the most 75 | applicable error type and status code for a given scenario. Should your given 76 | scenario require something more customized, the Error objects can be customized 77 | with an options object. 78 | 79 | ## Getting Started 80 | 81 | Install the module with: `npm install restify-errors` 82 | 83 | For TypeScript type definitions: `npm install @types/restify-errors` 84 | 85 | ## Usage 86 | 87 | ### Migration from 5.x to 6.x 88 | 89 | As of 6.x this module is now a thin wrapper over the 90 | [VError](https://github.com/davepacheco/node-verror) module. Every Error 91 | constructor exposed by this module inherits from VError, which means the 92 | constructor signatures are now also identical to VError. 93 | 94 | All VError static methods are also re-exported on the restify-errors export 95 | object. For all intents and purposes, you should treat this library as an 96 | extension of VError, with a list of built in constructors and sugar functions. 97 | 98 | The primary difference between the old 5.x and 6.x API is a reshuffling of the 99 | option names and where they are provided. In 5.x: 100 | 101 | ```js 102 | const err = new errors.InternalServerError(priorErr, { 103 | message: 'boom!', 104 | context: { foo: 'bar' } 105 | }); 106 | ``` 107 | 108 | In 6.x: 109 | 110 | ```js 111 | const err = new errors.InternalServerError({ 112 | cause: priorErr, 113 | info: { foo: 'bar' } 114 | }, 'boom!'); 115 | ``` 116 | 117 | ### Context/Info object 118 | In 5.x, the `.context` property was used to store and capture context about the 119 | scenario causing the error. This concept is still supported, but now uses 120 | VError's info object to achieve the same thing. As it uses the VError APIs, all 121 | you have to now is pass `info` instead of `context` when creating an Error. 122 | 123 | For migration purposes, accessing the info object via `.context` will be 124 | supported through 6.x, and the serializer will also continue to support it. 125 | Both may be deprecated in future versions. To access the info object, you can 126 | use the VError static method `.info()`, which is re-exported on the 127 | restify-errors exports: 128 | 129 | ```js 130 | var errors = require('restify-errors'); 131 | var nerror = require('@netflix/nerror'); 132 | 133 | var err = new errors.InternalServerError({ 134 | info: { 135 | foo: 'bar' 136 | } 137 | }); 138 | errors.info(err); // => { foo: 'bar' } 139 | verror.info(err); // => { foo: 'bar' } 140 | ``` 141 | 142 | Note that using verror directly also works, since all Error objects created by 143 | this library inherit from VError. 144 | 145 | ### Custom constructors 146 | 147 | In 5.x, using the `makeConstructor` class would add the constructor itself to 148 | restify-error's module.exports object. This was problematic in complex 149 | applications, where custom Error constructors could be shared across multiple 150 | modules in multiple contexts. 151 | 152 | As a result, in 6.x, custom constructors are no longer stored on the 153 | module.exports object, and it is the user's responsibility to retain a 154 | reference to those custom constructors. 155 | 156 | 157 | ### Creating Errors 158 | 159 | In your application, create errors by using the constructors: 160 | 161 | ```js 162 | var errors = require('restify-errors'); 163 | 164 | server.get('/foo', function(req, res, next) { 165 | 166 | if (!req.query.foo) { 167 | return next(new errors.BadRequestError()); 168 | } 169 | 170 | res.send(200, 'ok!'); 171 | return next(); 172 | }); 173 | ``` 174 | 175 | ### Checking Error types 176 | 177 | You can easily do instance checks against the Error objects: 178 | 179 | ```js 180 | function redirectIfErr(req, res, next) { 181 | var err = req.data.error; 182 | if (err) { 183 | if (err instanceof errors.InternalServerError) { 184 | next(err); 185 | } else if (err instanceof errors.NotFoundError) { 186 | res.redirect('/NotFound', next); 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | You can also check against the `.code` or `.name` properties in case there are 193 | multiple copies of restify-error in your application process: 194 | 195 | ```js 196 | function redirectIfErr(req, res, next) { 197 | var err = req.data.error; 198 | if (err) { 199 | if (err.name === 'InternalServerError' || 200 | err.code === 'InternalServer') { 201 | next(err); 202 | } else if (err instanceof errors.NotFoundError) { 203 | res.redirect('/NotFound', next); 204 | } 205 | } 206 | } 207 | ``` 208 | 209 | ### Serializing Errors 210 | 211 | All Error objects in this module ship with both a `toString()` and `toJSON()` 212 | methods. Restify uses these methods to "render" errors when they are passed to 213 | `res.send()`: 214 | 215 | ```js 216 | function render(req, res, next) { 217 | res.send(new errors.InternalServerError()); 218 | return next(); 219 | } 220 | 221 | // => restify will render an application/json response with an http 500: 222 | // { 223 | // code: 'InternalServerError', 224 | // message: '' 225 | // } 226 | ``` 227 | 228 | You can override either of these methods to customize the serialization of an 229 | error. 230 | 231 | ### Customizing Errors 232 | 233 | If you'd like to change the status code or message of a built-in Error, you can 234 | pass an options object to the constructor: 235 | 236 | ```js 237 | function render(req, res, next) { 238 | var myErr = new errors.InvalidVersionError({ 239 | statusCode: 409 240 | }, 'Version not supported with current query params'); 241 | 242 | res.send(myErr); 243 | return next(); 244 | } 245 | 246 | // => even though InvalidVersionError has a built-in status code of 400, it 247 | // has been customized with a 409 status code. restify will now render an 248 | // application/json response with an http 409: 249 | // { 250 | // code: 'InvalidVersionError', 251 | // message: 'Version not supported with current query params' 252 | // } 253 | ``` 254 | 255 | ### Passing in prior errors (causes) 256 | 257 | Like [WError](https://github.com/davepacheco/node-verror), all constructors 258 | accept an Error object as the first argument to build rich Error objects and 259 | stack traces. Assume a previous file lookup failed and an error was passed on: 260 | 261 | ```js 262 | function wrapError(req, res, next) { 263 | 264 | if (req.error) { 265 | var myErr = new errors.InternalServerError(req.error, 'bad times!'); 266 | return next(myErr); 267 | } 268 | return next(); 269 | } 270 | ``` 271 | 272 | This will allow Error objects to maintain context from previous errors, giving 273 | you full visibility into what caused an underlying issue: 274 | 275 | ```js 276 | console.log(myErr.message); 277 | // => 'bad times!' 278 | 279 | console.log(myErr.toString()); 280 | // => InternalServerError: bad times!; caused by Error: file lookup failed! 281 | 282 | // if you're using Bunyan, you'll get rich stack traces: 283 | bunyanLogger.info(myErr); 284 | 285 | InternalServerError: bad times! 286 | at Object. (/Users/restify/test.js:30:16) 287 | at Module._compile (module.js:460:26) 288 | at Object.Module._extensions..js (module.js:478:10) 289 | at Module.load (module.js:355:32) 290 | at Function.Module._load (module.js:310:12) 291 | at Function.Module.runMain (module.js:501:10) 292 | at startup (node.js:129:16) 293 | at node.js:814:3 294 | Caused by: Error: file lookup failed! 295 | at Object. (/Users/restify/test.js:29:15) 296 | at Module._compile (module.js:460:26) 297 | at Object.Module._extensions..js (module.js:478:10) 298 | at Module.load (module.js:355:32) 299 | at Function.Module._load (module.js:310:12) 300 | at Function.Module.runMain (module.js:501:10) 301 | at startup (node.js:129:16) 302 | at node.js:814:3 303 | ``` 304 | 305 | ### Bunyan/Pino support 306 | 307 | Since errors created via restify-errors inherit from VError, you'll get out of 308 | the box support via bunyan's standard serializers. If you are using the 309 | `info` property, you can use the serializer shipped with restify-errors: 310 | 311 | ```js 312 | var bunyan = require('bunyan'); 313 | var restifyErrors = require('restify-errors'); 314 | 315 | var log = bunyan.createLogger({ 316 | name: 'myLogger', 317 | serializers: { 318 | err: restifyErrors.bunyanSerializer 319 | } 320 | }); 321 | 322 | var err = new restifyErrors.InternalServerError({ 323 | info: { 324 | foo: 'bar', 325 | bar: 1 326 | } 327 | }, 'cannot service this request'); 328 | 329 | log.error(err, 'oh noes'); 330 | ``` 331 | 332 | ```sh 333 | [2016-08-31T22:27:13.117Z] ERROR: log/51633 on laptop: oh noes (err.code=InternalServer) 334 | InternalServerError: cannot service this request! (foo="bar", bar=1) 335 | at Object. (/restify/test.js:11:11) 336 | at Module._compile (module.js:409:26) 337 | at Object.Module._extensions..js (module.js:416:10) 338 | at Module.load (module.js:343:32) 339 | at Function.Module._load (module.js:300:12) 340 | at Function.Module.runMain (module.js:441:10) 341 | at startup (node.js:139:18) 342 | at node.js:974:3 343 | ``` 344 | 345 | You can, of course, combine this with the standard set of serializers that 346 | bunyan ships with. VError's MultiError is also supported: 347 | 348 | ```js 349 | var underlyingErr = new Error('boom'); 350 | var multiErr = new verror.MultiError([ 351 | new Error('boom'), 352 | new restifyErrors.InternalServerError({ 353 | cause: underlyingErr, 354 | info: { 355 | foo: 'bar', 356 | baz: 1 357 | } 358 | }, 'wrapped') 359 | ]); 360 | 361 | log.error(multiErr, 'oh noes'); 362 | ``` 363 | 364 | ``` 365 | [2016-08-31T22:48:43.244Z] ERROR: logger/55311 on laptop: oh noes 366 | MultiError 1 of 2: Error: boom 367 | at Object. (/restify/test.js:16:5) 368 | at Module._compile (module.js:409:26) 369 | at Object.Module._extensions..js (module.js:416:10) 370 | at Module.load (module.js:343:32) 371 | at Function.Module._load (module.js:300:12) 372 | at Function.Module.runMain (module.js:441:10) 373 | at startup (node.js:139:18) 374 | at node.js:974:3 375 | MultiError 2 of 2: InternalServerError: wrapped (foo="bar", baz=1) 376 | at Object. (/restify/test.js:17:5) 377 | at Module._compile (module.js:409:26) 378 | at Object.Module._extensions..js (module.js:416:10) 379 | at Module.load (module.js:343:32) 380 | at Function.Module._load (module.js:300:12) 381 | at Function.Module.runMain (module.js:441:10) 382 | at startup (node.js:139:18) 383 | at node.js:974:3 384 | Caused by: Error: boom 385 | at Object. (/restify/test.js:14:21) 386 | at Module._compile (module.js:409:26) 387 | at Object.Module._extensions..js (module.js:416:10) 388 | at Module.load (module.js:343:32) 389 | at Function.Module._load (module.js:300:12) 390 | at Function.Module.runMain (module.js:441:10) 391 | at startup (node.js:139:18) 392 | at node.js:974:3 393 | ``` 394 | 395 | For more information about building rich errors, check out 396 | [VError](https://github.com/davepacheco/node-verror). 397 | 398 | 399 | #### Customizing the serializer 400 | 401 | The serializer can also be customized. The serializer currently supports 402 | the following options: 403 | 404 | * `options.topLevelFields` {Boolean} - if true, serializes all top level fields 405 | found on the error object, minus "known" Error/VError fields. This can be 406 | useful if errors are created in dependencies that don't use VError or 407 | restify-errors to maintain context in an independent object. 408 | 409 | For example: 410 | 411 | ```js 412 | var bunyan = require('bunyan'); 413 | var restifyErrors = require('restify-errors'); 414 | 415 | var log = bunyan.createLogger({ 416 | name: 'myLogger', 417 | serializers: restifyErrors.bunyanSerializer.create({ 418 | topLevelFields: true 419 | }) 420 | }); 421 | 422 | var err = new Error('pull!'); 423 | err.espresso = 'normale'; 424 | 425 | log.error(err, 'oh noes!'); 426 | ``` 427 | 428 | ```sh 429 | [2018-05-22T01:32:25.164Z] ERROR: myLogger/61085 on laptop: oh noes! 430 | Error: pull! (espresso="normale") 431 | at Object. (/restify/serializer.js:11:11) 432 | at Module._compile (module.js:577:32) 433 | at Object.Module._extensions..js (module.js:586:10) 434 | at Module.load (module.js:494:32) 435 | at tryModuleLoad (module.js:453:12) 436 | at Function.Module._load (module.js:445:3) 437 | at Module.runMain (module.js:611:10) 438 | at run (bootstrap_node.js:387:7) 439 | at startup (bootstrap_node.js:153:9) 440 | ``` 441 | 442 | 443 | 444 | 445 | ### Subclassing Errors 446 | 447 | You can also create your own Error subclasses by using the provided 448 | `makeConstructor()` method. 449 | 450 | ```js 451 | errors.makeConstructor('ExecutionError', { 452 | statusCode: 406, 453 | failureType: 'motion', 454 | message: 'my default message' 455 | }); 456 | var myErr = new errors.ExecutionError('bad joystick input!'); 457 | 458 | console.log(myErr instanceof ExecutionError); 459 | // => true 460 | 461 | console.log(myErr.message); 462 | // => 'ExecutionError: bad joystick input!' 463 | 464 | console.log(myErr.failureType); 465 | // => 'motion' 466 | 467 | console.log(myErr.statusCode); 468 | // => 406 469 | 470 | console.log(myErr.stack); 471 | 472 | ExecutionError: bad joystick input! 473 | at Object. (/Users/restify/test.js:30:16) 474 | at Module._compile (module.js:460:26) 475 | at Object.Module._extensions..js (module.js:478:10) 476 | at Module.load (module.js:355:32) 477 | at Function.Module._load (module.js:310:12) 478 | at Function.Module.runMain (module.js:501:10) 479 | at startup (node.js:129:16) 480 | at node.js:814:3 481 | ``` 482 | 483 | 484 | ## API 485 | 486 | All Error constructors are variadic and accept the following signatures, which 487 | are identical to the 488 | [VError and WError](https://github.com/davepacheoco/node-verror) signatures. 489 | 490 | ### new Error(sprintf_args...) 491 | ### new Error(priorErr [, sprintf_args...]) 492 | ### new Error(options [, sprinf_args...]) 493 | 494 | restify-errors adds additional options for the final signature: 495 | 496 | * `options.restCode` {Number} - a description code for your Error. This is used 497 | by restify to render an error when it is directly passed to `res.send()`. By 498 | default, it is the name of your error constructor (e.g., the restCode for a 499 | BadDigestError is BadDigest). 500 | * `options.statusCode` {Number} - an http status code 501 | * `options.toJSON` {Function} - override the default `toJSON()` method 502 | * `options.toString` {Function} - override the default `toString()` method 503 | 504 | ### makeConstructor(name [, defaults]) 505 | 506 | Creates a custom Error constructor, adds it to the existing exports object. 507 | 508 | * `name` {String} - the name of your Error 509 | * `defaults` {Object} - an object of default values that will added to the 510 | prototype. It is possible to override the default values for `restCode`, 511 | `statusCode`, `toString()` and `toJSON()`. 512 | 513 | **Returns:** {Constructor} 514 | 515 | ### makeErrFromCode(statusCode [, args...]) 516 | 517 | Create an Error object using an http status code. This uses `http` module's 518 | `STATUS_CODES` to do the status code lookup. Thus, this convenience method 519 | is useful only for creating HttpErrors, and not RestErrors. 520 | 521 | * `statusCode` {Number} - an http status code 522 | * `args` - arguments to be passed on to the constructor 523 | 524 | **Returns:** {Object} an Error object 525 | 526 | 527 | ## Contributing 528 | 529 | Add unit tests for any new or changed functionality. Ensure that lint and style 530 | checks pass. 531 | 532 | To start contributing, install the git pre-push hooks: 533 | 534 | ```sh 535 | make githooks 536 | ``` 537 | 538 | Before committing, run the prepush hook: 539 | 540 | ```sh 541 | make prepush 542 | ``` 543 | 544 | If you have style errors, you can auto fix whitespace issues by running: 545 | 546 | ```sh 547 | make codestyle-fix 548 | ``` 549 | 550 | ## License 551 | 552 | Copyright (c) 2018 Alex Liu 553 | 554 | Licensed under the MIT license. 555 | -------------------------------------------------------------------------------- /lib/baseClasses/HttpError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // core modules 4 | var util = require('util'); 5 | 6 | // external modules 7 | var assert = require('assert-plus'); 8 | var nerror = require('@netflix/nerror'); 9 | 10 | // internal files 11 | var helpers = require('./../helpers'); 12 | 13 | var WError = nerror.WError; 14 | 15 | 16 | /** 17 | * Base HttpError class. inherits from WError. 18 | * Variadic signature, first two are special to Restify, using a options obj. 19 | * 1) new HttpError(anotherErr, {...}); 20 | * 2) new HttpError({...}); 21 | * Last one is a straight pass through to WError 22 | * 3) new HttpError('my special error message'); 23 | * @public 24 | * @class 25 | */ 26 | function HttpError() { 27 | 28 | var self = this; 29 | var parsed = helpers.parseVErrorArgs(arguments); 30 | var verrorArgs = (parsed.verrorArgs.length !== 0) ? 31 | parsed.verrorArgs : 32 | [ self.message ]; 33 | var opts = parsed.internalOpts; 34 | 35 | // if we have opts for use with restify-error's constructors, assert on them 36 | // now. 37 | assert.optionalNumber(opts.statusCode, 'opts.statusCode'); 38 | assert.optionalFunc(opts.toJSON, 'opts.toJSON'); 39 | assert.optionalFunc(opts.toString, 'opts.toString'); 40 | 41 | // inherit from WError, call super first before doing anything else! WError 42 | // will handle calling captureStackTrace, using arguments.callee to 43 | // eliminate unnecessary stack frames. 44 | WError.apply(self, verrorArgs); 45 | 46 | /** 47 | * the http status code of the error. 48 | * because inherited classes have status code set on the prototype, 49 | * only assign a status code if truthy. 50 | * @property 51 | * @type {Number} 52 | */ 53 | if (opts.statusCode) { 54 | self.statusCode = opts.statusCode; 55 | } 56 | 57 | /** 58 | * property used to describe the error. emulates some of the core module 59 | * errors that have codes on them to describe their nature, 60 | * i.e., fs.readFile can return an ENOENT error where err.code is 'ENOENT' 61 | * @property 62 | * @type {String} 63 | */ 64 | if (opts.code) { 65 | self.code = opts.code; 66 | } 67 | 68 | /** 69 | * an object used to render the error when serialized (JSON.stringify or 70 | * toString) 71 | * @property 72 | * @type {Object} 73 | */ 74 | self.body = { 75 | // err.code/err.restCode is used by legacy restify paths, probably 76 | // originally created to emulate the code property that is created by 77 | // some native core module errors (i.e., a failed fs.readFile will 78 | // return a ENOENT error with a err.code of ENOENT). 79 | // 80 | // for Http/RestErrors, the code will be the error name but with 81 | // 'error' truncated from the string. i.e., if the error name is 82 | // InternalServerError, the code is InternalServer. 83 | code: opts.code || self.code, 84 | message: self.message || '' 85 | }; 86 | 87 | /** 88 | * override prototype toJSON method. must use 'hasOwnProperty' to ensure we 89 | * are picking up a user specified option vs the one set on object literals. 90 | * @property 91 | * @type {Function} 92 | */ 93 | if (Object.prototype.hasOwnProperty.call(opts, 'toJSON')) { 94 | self.toJSON = opts.toJSON; 95 | } 96 | 97 | /** 98 | * override prototype toJSON method. must use 'hasOwnProperty' to ensure we 99 | * are picking up a user specified option vs the one set on object literals. 100 | * @property 101 | * @type {Function} 102 | */ 103 | if (Object.prototype.hasOwnProperty.call(opts, 'toString')) { 104 | self.toString = opts.toString; 105 | } 106 | } 107 | util.inherits(HttpError, WError); 108 | 109 | /** 110 | * migration method to allow continued use of `.context` property that has now 111 | * been migrated to use VError's info object under the hood. 112 | * @type {Object} 113 | */ 114 | Object.defineProperty(HttpError.prototype, 'context', { 115 | get: function getContext() { 116 | var self = this; 117 | return nerror.info(self); 118 | } 119 | }); 120 | 121 | /** 122 | * assign non-standard display name property on the CONSTRUCTOR (not prototype), 123 | * which is supported by all VMs. useful for stack trace output. 124 | * @type {String} 125 | */ 126 | HttpError.displayName = 'HttpError'; 127 | 128 | /** 129 | * the name of the error, used in the stack trace output 130 | * @type {String} 131 | */ 132 | HttpError.prototype.name = 'HttpError'; 133 | 134 | /** 135 | * the default error code 136 | * @type {String} 137 | */ 138 | HttpError.prototype.code = 'Error'; 139 | 140 | 141 | /** 142 | * implement a basic toString/JSON.stringify. they should be identical. 143 | * @public 144 | * @method toJSON 145 | * @returns {String} 146 | */ 147 | HttpError.prototype.toJSON = function toJSON() { 148 | var self = this; 149 | var message = ''; 150 | 151 | // if we have a cause, get the full VError toString() without the current 152 | // error name. verbose check, self.cause can exist but returns undefined 153 | if (self.cause && typeof self.cause === 'function' && self.cause()) { 154 | var fullString = self.toString(); 155 | message = fullString.substr(fullString.indexOf(' ') + 1); 156 | } else { 157 | message = self.body.message; 158 | } 159 | 160 | return { 161 | code: self.body.code, 162 | message: message 163 | }; 164 | }; 165 | 166 | 167 | 168 | module.exports = HttpError; 169 | -------------------------------------------------------------------------------- /lib/baseClasses/RestError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var _ = require('lodash'); 5 | 6 | var HttpError = require('./HttpError'); 7 | var helpers = require('./../helpers'); 8 | 9 | 10 | /** 11 | * Base RestError class. inherits from WError. 12 | * Variadic signature, first two are special to Restify, using a opts obj. 13 | * 1) new RestError(anotherErr, {...}); 14 | * 2) new RestError({...}); 15 | * Last one is a straight pass through to WError 16 | * 3) new RestError('my special error message'); 17 | * @public 18 | * @class 19 | */ 20 | function RestError() { 21 | 22 | var self = this; 23 | var parsed = helpers.parseVErrorArgs(arguments); 24 | var opts = parsed.internalOpts; 25 | 26 | // call super 27 | HttpError.apply(self, _.toArray(arguments)); 28 | 29 | /** 30 | * a bit of a misnomer, not really an http code, but rather the name 31 | * of the error. the equivalent of HttpCode's `code` property. 32 | * TODO: Not sure why the default here is 'Error' and not 'RestError'? 33 | * only set the value if it doesnt already exist, as it's defined on the 34 | * prototype for subclasses. 35 | * @property 36 | * @type {String} 37 | */ 38 | if (opts.restCode) { 39 | self.restCode = opts.restCode; 40 | } 41 | 42 | /** 43 | * an object used to render the error when passed 44 | * to res.send() 45 | * @property 46 | * @type {Object} 47 | */ 48 | self.body = { 49 | // err.code/err.restCode is used by legacy restify paths, probably 50 | // originally created to emulate the code property that is created by 51 | // some native core module errors (i.e., a failed fs.readFile will 52 | // return a ENOENT error with a err.code of ENOENT). 53 | // 54 | // for Http/RestErrors, the code will be the error name but with 55 | // 'error' truncated from the string. i.e., if the error name is 56 | // InternalServerError, the code is InternalServer. 57 | code: opts.restCode || self.restCode, 58 | message: self.message || '' 59 | }; 60 | } 61 | util.inherits(RestError, HttpError); 62 | 63 | /** 64 | * assign non-standard display name property on the CONSTRUCTOR (not prototype), 65 | * which is supported by all VMs. useful for stack trace output. 66 | * @type {String} 67 | */ 68 | RestError.displayName = 'RestError'; 69 | 70 | /** 71 | * the name of the error, used in the stack trace output 72 | * @type {String} 73 | */ 74 | RestError.prototype.name = 'RestError'; 75 | 76 | /** 77 | * the default rest code. i.e., a BadDigestError has a restCode of 'BadDigest'. 78 | * @type {String} 79 | */ 80 | RestError.prototype.restCode = 'Error'; 81 | 82 | 83 | module.exports = RestError; 84 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // core modules 4 | var http = require('http'); 5 | 6 | // external modules 7 | var _ = require('lodash'); 8 | var assert = require('assert-plus'); 9 | 10 | // local globals 11 | var INTERNAL_OPTS_KEYS = { 12 | 'code': true, 13 | 'restCode': true, 14 | 'statusCode': true, 15 | 'toJSON': true, 16 | 'toString': true 17 | }; 18 | 19 | 20 | //------------------------------------------------------------------------------ 21 | // constructor arg parsers 22 | //------------------------------------------------------------------------------ 23 | 24 | /** 25 | * helper function for parsing all of the Error constructor variadic sigs. 26 | * these signatures are all derived from VError. 27 | * 1) new HttpError(sprintf_args...); 28 | * 2) new HttpError(anotherErr, sprintf_args); 29 | * 3) new HttpError({...}, sprintf_args); 30 | * restify-errors' value is to add support for additional options using the 31 | * signature #3. this function parses out the arguments specific to 32 | * restify-errors so that they don't get passed on to VError. 33 | * @public 34 | * @param {Object} ctorArgs an 'arguments' object 35 | * @function parseVErrorArgs 36 | * @returns {Object} 37 | */ 38 | function parseVErrorArgs(ctorArgs) { 39 | // to array the inner arguments so it's easier to determine which cases 40 | // we are looking at 41 | var args = _.toArray(ctorArgs); 42 | var internalOpts = {}; 43 | var verrorOpts = {}; 44 | var verrorArgs; 45 | 46 | if (_.isPlainObject(args[0])) { 47 | // split restify-errors options from verror options 48 | _.forOwn(args[0], function(val, key) { 49 | if (Object.prototype.hasOwnProperty.call(INTERNAL_OPTS_KEYS, key)) { 50 | internalOpts[key] = val; 51 | } else { 52 | verrorOpts[key] = val; 53 | } 54 | }); 55 | 56 | // reconstruct verror ctor options from the cleaned up options 57 | verrorArgs = [ verrorOpts ].concat(_.tail(args)); 58 | } else { 59 | verrorArgs = args; 60 | } 61 | 62 | return { 63 | // raw arguments to pass to VError constructor 64 | verrorArgs: verrorArgs, 65 | // restify-errors specific options 66 | internalOpts: internalOpts 67 | }; 68 | } 69 | 70 | 71 | //------------------------------------------------------------------------------ 72 | // helpers 73 | //------------------------------------------------------------------------------ 74 | 75 | 76 | /** 77 | * create an error name from a status code. looks up the description via 78 | * http.STATUS_CODES, then calls createErrNameFromDesc(). 79 | * @private 80 | * @function errNameFromCode 81 | * @param {Number} code an http status code 82 | * @returns {String} 83 | */ 84 | function errNameFromCode(code) { 85 | 86 | assert.number(code, 'code'); 87 | 88 | // attempt to retrieve status code description, if not available, 89 | // fallback on 500. 90 | var errorDesc = http.STATUS_CODES[code] || http.STATUS_CODES[500]; 91 | return errNameFromDesc(errorDesc); 92 | } 93 | 94 | 95 | /** 96 | * used to programatically create http error code names, using the underlying 97 | * status codes names exposed via the http module. 98 | * @private 99 | * @function errNameFromDesc 100 | * @param {String} desc a description of the error, e.g., 'Not Found' 101 | * @returns {String} 102 | */ 103 | function errNameFromDesc(desc) { 104 | 105 | assert.string(desc, 'desc'); 106 | 107 | // takes an error description, split on spaces, camel case it correctly, 108 | // then append 'Error' at the end of it. 109 | // e.g., the passed in description is 'Internal Server Error' 110 | // the output is 'InternalServerError' 111 | var pieces = desc.split(/\s+/); 112 | var name = _.reduce(pieces, function(acc, piece) { 113 | // lowercase all, then capitalize it. 114 | var normalizedPiece = _.capitalize(piece.toLowerCase()); 115 | return acc + normalizedPiece; 116 | }, ''); 117 | 118 | // strip all non word characters 119 | name = name.replace(/\W+/g, ''); 120 | 121 | // append 'Error' at the end of it only if it doesn't already end with it. 122 | if (!_.endsWith(name, 'Error')) { 123 | name += 'Error'; 124 | } 125 | 126 | return name; 127 | } 128 | 129 | 130 | 131 | module.exports = { 132 | errNameFromCode: errNameFromCode, 133 | errNameFromDesc: errNameFromDesc, 134 | parseVErrorArgs: parseVErrorArgs 135 | }; 136 | -------------------------------------------------------------------------------- /lib/httpErrors.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | // core modules 6 | var http = require('http'); 7 | var util = require('util'); 8 | 9 | // external modules 10 | var _ = require('lodash'); 11 | 12 | // local files 13 | var helpers = require('./helpers'); 14 | var HttpError = require('./baseClasses/HttpError'); 15 | 16 | 17 | // Programatically create 4xx and 5xx HTTP status codes Error classes 18 | // This list includes: 19 | // BadRequestError 20 | // UnauthorizedError 21 | // PaymentRequiredError 22 | // ForbiddenError 23 | // NotFoundError 24 | // MethodNotAllowedError 25 | // NotAcceptableError 26 | // ProxyAuthenticationRequiredError 27 | // RequestTimeoutError 28 | // ConflictError 29 | // GoneError 30 | // LengthRequiredError 31 | // PreconditionFailedError 32 | // RequestEntityTooLargeError 33 | // RequesturiTooLargeError 34 | // UnsupportedMediaTypeError 35 | // RangeNotSatisfiableError (For Node >= 4 & iojs >= 3) 36 | // RequestedRangeNotSatisfiableError (For Node 0.x & iojs < 3) 37 | // ExpectationFailedError 38 | // ImATeapotError 39 | // UnprocessableEntityError 40 | // LockedError 41 | // FailedDependencyError 42 | // UnorderedCollectionError 43 | // UpgradeRequiredError 44 | // PreconditionRequiredError 45 | // TooManyRequestsError 46 | // RequestHeaderFieldsTooLargeError 47 | // InternalServerError 48 | // NotImplementedError 49 | // BadGatewayError 50 | // ServiceUnavailableError 51 | // GatewayTimeoutError 52 | // HttpVersionNotSupportedError 53 | // VariantAlsoNegotiatesError 54 | // InsufficientStorageError 55 | // BandwidthLimitExceededError 56 | // NotExtendedError 57 | // NetworkAuthenticationRequiredError 58 | var httpErrors = _.reduce(http.STATUS_CODES, function(acc, desc, code) { 59 | 60 | var parsedCode = parseInt(code, 10); 61 | 62 | if (parsedCode >= 400) { 63 | var name = helpers.errNameFromDesc(desc); 64 | 65 | // this is a dynamic constructor for an error message. 66 | // arguments are variadic. constructor fn name must be anonymous. 67 | /** 68 | * Variadic signature, first two are special to Restify, using a 69 | * options obj. 70 | * 1) new [Dynamic]Error(anotherErr, {...}); 71 | * 2) new [Dynamic]Error({...}); 72 | * Last one is a straight pass through to WError 73 | * 3) new [Dynamic]Error('my special error message'); 74 | * @public 75 | * @class 76 | */ 77 | acc[name] = function() { 78 | // call super 79 | HttpError.apply(this, arguments); 80 | }; 81 | util.inherits(acc[name], HttpError); 82 | 83 | /** 84 | * assign non-standard display name property on the CONSTRUCTOR (not 85 | * prototype), which is supported by all VMs. useful for stack trace 86 | * output. 87 | * @type {String} 88 | */ 89 | acc[name].displayName = name; 90 | 91 | /** 92 | * the name of the error, used in the stack trace output 93 | * @type {String} 94 | */ 95 | acc[name].prototype.name = name; 96 | 97 | /** 98 | * assign a default status code based on core http module. 99 | * users can override this if they want to. HttpError constructor 100 | * will handle overriding at the instance level. 101 | * @type {Number} 102 | */ 103 | acc[name].prototype.statusCode = parsedCode; 104 | 105 | /** 106 | * default code is the error name 107 | * @type {String} 108 | */ 109 | acc[name].prototype.code = name.replace(new RegExp('Error$'), ''); 110 | } 111 | 112 | return acc; 113 | }, {}); 114 | 115 | 116 | 117 | module.exports = httpErrors; 118 | 119 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var assert = require('assert-plus'); 5 | var nerror = require('@netflix/nerror'); 6 | 7 | var bunyanSerializer = require('./serializer'); 8 | var helpers = require('./helpers'); 9 | var HttpError = require('./baseClasses/HttpError'); 10 | var RestError = require('./baseClasses/RestError'); 11 | var httpErrors = require('./httpErrors'); 12 | var restErrors = require('./restErrors'); 13 | var makeConstructor = require('./makeConstructor'); 14 | 15 | 16 | /** 17 | * create an error object from an http status code. 18 | * first arg is status code, all subsequent args 19 | * passed on to the constructor. only works for regular 20 | * HttpErrors, not RestErrors. 21 | * @public 22 | * @function makeErrFromCode 23 | * @param {Number} statusCode the http status code 24 | * @returns {Error} an error instance 25 | */ 26 | function makeErrFromCode(statusCode) { 27 | // assert! 28 | assert.number(statusCode, 'statusCode'); 29 | assert.equal(statusCode >= 400, true); 30 | 31 | // drop the first arg 32 | var args = _.drop(_.toArray(arguments)); 33 | var name = helpers.errNameFromCode(statusCode); 34 | 35 | var ErrCtor = httpErrors[name]; 36 | 37 | // assert constructor was found 38 | assert.func(ErrCtor); 39 | 40 | // pass every other arg down to constructor 41 | return makeInstance(ErrCtor, makeErrFromCode, args); 42 | } 43 | 44 | 45 | /** 46 | * helper function to dynamically apply args 47 | * to a dynamic constructor. magicks. 48 | * @private 49 | * @function makeInstance 50 | * @param {Function} constructor the constructor function 51 | * @param {Function} constructorOpt where to start the error stack trace 52 | * @param {Array} args array of arguments to apply to ctor 53 | * @returns {Object} instance of the ctor 54 | */ 55 | function makeInstance(constructor, constructorOpt, args) { 56 | // pass args to the constructor 57 | function F() { // eslint-disable-line require-jsdoc 58 | return constructor.apply(this, args); 59 | } 60 | F.prototype = constructor.prototype; 61 | 62 | // new up an instance, and capture stack trace from the 63 | // passed in constructorOpt 64 | var errInstance = new F(); 65 | Error.captureStackTrace(errInstance, constructorOpt); 66 | 67 | // return the error instance 68 | return errInstance; 69 | } 70 | 71 | 72 | 73 | module.exports = _.assign({}, httpErrors, restErrors, nerror, { 74 | // export base classes 75 | HttpError: HttpError, 76 | RestError: RestError, 77 | 78 | // export convenience functions 79 | makeConstructor: makeConstructor, 80 | makeErrFromCode: makeErrFromCode, 81 | 82 | // deprecated method names, how long do we keep these for? 83 | // restify has already been updated, but what about external consumers? 84 | codeToHttpError: makeErrFromCode, 85 | 86 | // built in bunyan serializer 87 | bunyanSerializer: bunyanSerializer 88 | }); 89 | -------------------------------------------------------------------------------- /lib/makeConstructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // core modules 4 | var util = require('util'); 5 | 6 | // external modules 7 | var _ = require('lodash'); 8 | var assert = require('assert-plus'); 9 | 10 | // local files 11 | var RestError = require('./baseClasses/RestError'); 12 | 13 | 14 | 15 | /** 16 | * create RestError subclasses for users. takes a string, creates a 17 | * constructor for them. magicks, again. 18 | * @public 19 | * @function makeConstructor 20 | * @param {String} name the name of the error class to create 21 | * @param {Number} defaults optional status code 22 | * @return {Function} a constructor function 23 | */ 24 | function makeConstructor(name, defaults) { 25 | 26 | assert.string(name, 'name'); 27 | assert.optionalObject(defaults, 'defaults'); 28 | 29 | // code property doesn't have 'Error' in it. remove it. 30 | var defaultCode = name.replace(new RegExp('[Ee]rror$'), ''); 31 | var prototypeDefaults = _.assign({}, { 32 | name: name, 33 | code: (defaults && defaults.code) || defaultCode, 34 | restCode: _.get(defaults, 'restCode', defaultCode) 35 | }, defaults); 36 | 37 | // assert that this constructor doesn't already exist. 38 | assert.equal( 39 | typeof module.exports[name], 40 | 'undefined', 41 | 'Constructor already exists!' 42 | ); 43 | 44 | // dynamically create a constructor. 45 | // must be anonymous fn. 46 | var ErrCtor = function() { // eslint-disable-line require-jsdoc, func-style 47 | // call super 48 | RestError.apply(this, arguments); 49 | this.name = name; 50 | }; 51 | util.inherits(ErrCtor, RestError); 52 | 53 | // copy over all options to prototype 54 | _.assign(ErrCtor.prototype, prototypeDefaults); 55 | 56 | // assign display name 57 | ErrCtor.displayName = name; 58 | 59 | // return constructor to user, they can choose how to store and manage it. 60 | return ErrCtor; 61 | } 62 | 63 | 64 | module.exports = makeConstructor; 65 | -------------------------------------------------------------------------------- /lib/restErrors.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var util = require('util'); 6 | 7 | var _ = require('lodash'); 8 | 9 | var RestError = require('./baseClasses/RestError'); 10 | 11 | 12 | //------------------------------------------------------------------------------ 13 | // local global vars 14 | //------------------------------------------------------------------------------ 15 | 16 | var CODES = { 17 | BadDigest: 400, 18 | BadMethod: 405, 19 | ConnectTimeout: 408, 20 | Internal: 500, 21 | InvalidArgument: 409, 22 | InvalidContent: 400, 23 | InvalidCredentials: 401, 24 | InvalidHeader: 400, 25 | InvalidVersion: 400, 26 | MissingParameter: 409, 27 | NotAuthorized: 403, 28 | RequestExpired: 400, 29 | RequestThrottled: 429, 30 | ResourceNotFound: 404, 31 | WrongAccept: 406 32 | }; 33 | 34 | 35 | 36 | 37 | var restErrors = _.reduce(CODES, function(acc, statusCode, errorCode) { 38 | 39 | 40 | // append Error to the end of the name if it doesn't already end with it. 41 | // this becomes the name of the constructor 42 | var name = errorCode + 'Error'; 43 | 44 | // this is a dynamic constructor for an error message. 45 | // arguments are variadic. constructor fn name must be anonymous. 46 | /** 47 | * Variadic signature, first two are special to Restify, using a options 48 | * obj. 49 | * 1) new [Dynamic]Error(anotherErr, {...}); 50 | * 2) new [Dynamic]Error({...}); 51 | * Last one is a straight pass through to WError 52 | * 3) new [Dynamic]Error('my special error message'); 53 | * @public 54 | * @class 55 | */ 56 | acc[name] = function() { 57 | // call super 58 | RestError.apply(this, arguments); 59 | }; 60 | util.inherits(acc[name], RestError); 61 | 62 | /** 63 | * assign non-standard display name property on the CONSTRUCTOR (not 64 | * prototype), which is supported by all VMs. useful for stack trace 65 | * output. 66 | * @type {String} 67 | */ 68 | acc[name].displayName = name; 69 | 70 | /** 71 | * the name of the error, used in the stack trace output 72 | * @type {String} 73 | */ 74 | acc[name].prototype.name = name; 75 | 76 | /** 77 | * assign a default status code based on core http module. 78 | * users can override this if they want to. HttpError constructor 79 | * will handle overriding at the instance level. 80 | * @type {Number} 81 | */ 82 | acc[name].prototype.statusCode = statusCode; 83 | 84 | /** 85 | * the default rest code. i.e., a BadDigestError has a restCode of 86 | * 'BadDigest'. it is basically the key for the lookup in the CODES 87 | * mapping at the top of the file. 88 | * @type {String} 89 | */ 90 | acc[name].prototype.restCode = errorCode; 91 | 92 | 93 | return acc; 94 | }, {}); 95 | 96 | 97 | module.exports = restErrors; 98 | -------------------------------------------------------------------------------- /lib/serializer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // external modules 4 | var assert = require('assert-plus'); 5 | var EventEmitter = require('events'); 6 | var _ = require('lodash'); 7 | var nerror = require('@netflix/nerror'); 8 | var MultiError = nerror.MultiError; 9 | var VError = nerror.VError; 10 | var safeJsonStringify; 11 | 12 | // We load the domain module lazily to avoid performance regression on Node.js 13 | // v12. 14 | var domain; 15 | 16 | // try to require optional dependency 17 | try { 18 | // eslint-disable-next-line global-require 19 | safeJsonStringify = require('safe-json-stringify'); 20 | } catch (e) { 21 | safeJsonStringify = null; 22 | } 23 | 24 | 25 | /** 26 | * @class ErrorSerializer 27 | * @param {Object} opts an options object 28 | */ 29 | function ErrorSerializer(opts) { 30 | assert.object(opts, 'opts'); 31 | assert.bool(opts.topLevelFields, 'opts.topLevelFields'); 32 | 33 | /** 34 | * when true, serialize all top level fields found on the Error object 35 | * @type {Bool} 36 | */ 37 | this._serializeTopLevelFields = opts.topLevelFields; 38 | 39 | /** 40 | * find known fields we don't want to serialize 41 | * @type {Array} 42 | */ 43 | this._knownFields = this._findKnownFields(); 44 | } 45 | 46 | 47 | /** 48 | * loop through all errors() in a MultiError and build a stack trace 49 | * output. 50 | * @private 51 | * @method _getMultiErrorStack 52 | * @param {Object} err an error object 53 | * @returns {String} stack trace string 54 | */ 55 | ErrorSerializer.prototype._getMultiErrorStack = 56 | function _getMultiErrorStack(err) { 57 | 58 | var self = this; 59 | var out = ''; 60 | 61 | _.forEach(err.errors(), function(e, idx, errs) { 62 | out += 'MultiError ' + (idx + 1) + ' of ' + errs.length + ': '; 63 | out += self._getFullErrorStack(e) + '\n'; 64 | }); 65 | 66 | // remove last new line char 67 | out = out.slice(0, -1); 68 | 69 | return out; 70 | }; 71 | 72 | 73 | /** 74 | * loop through all cause() errors and build a stack trace output 75 | * @private 76 | * @method _getFullErrorStack 77 | * @param {Object} err an error object 78 | * @returns {String} stack trace string 79 | */ 80 | ErrorSerializer.prototype._getFullErrorStack = 81 | function _getFullErrorStack(err) { 82 | var self = this; 83 | var e = err; 84 | var out = ''; 85 | var first = true; 86 | 87 | do { 88 | if (first !== true) { 89 | out += '\nCaused by: '; 90 | } 91 | 92 | // parse out first new line of stack trace, append context there. 93 | var stackString = (e.stack || e.toString()).split('\n'); 94 | 95 | out += stackString.shift() + self._getSerializedContext(e); 96 | out += stackString.join('\n'); 97 | e = (typeof e.cause === 'function') ? e.cause() : null; 98 | first = false; 99 | } while (e); 100 | 101 | return out; 102 | }; 103 | 104 | 105 | /* eslint-disable max-len */ 106 | /* jscs:disable maximumLineLength */ 107 | /** 108 | * serialize the error context object into a string. borrows liberally from 109 | * bunyan's serializer: 110 | * https://github.com/trentm/node-bunyan/blob/6fdc5ff20965b81ab15f8f408fe11917e06306f6/lib/bunyan.js#L865 111 | * @private 112 | * @method _getSerializedContext 113 | * @param {Object} err an error object 114 | * @return {String} serialized context obj 115 | */ 116 | /* jscs:enable maximumLineLength */ 117 | /* eslint-enable max-len */ 118 | ErrorSerializer.prototype._getSerializedContext = 119 | function _getSerializedContext(err) { 120 | 121 | /** 122 | * serialize a POJO into a string of the format: 123 | * (key="valString", key2=valInteger, key3={a:valPojo}) 124 | * @param {Object} obj a POJO to serialize 125 | * @return {String} 126 | */ 127 | function serializeIntoEqualString(obj) { 128 | 129 | var out = ''; 130 | 131 | _.forEach(obj, function(val, key) { 132 | var stringVal; 133 | 134 | try { 135 | stringVal = JSON.stringify(val, safeCycles()); 136 | } catch (e) { 137 | if (safeJsonStringify) { 138 | stringVal = safeJsonStringify(val); 139 | } else { 140 | stringVal = 'unserializable! you can install ' + 141 | '"safe-json"stringify" module for safer ' + 142 | 'stringification'; 143 | } 144 | } 145 | 146 | out += key + '=' + stringVal + ', '; 147 | }); 148 | // remove last comma 149 | return out.slice(0, -2); 150 | } 151 | 152 | var self = this; 153 | var ret = ''; 154 | 155 | // look for error context in 3 places, in ascending order of precedence: 156 | // 1) raw fields on the error object that are not known verror or 157 | // restify-error fields 158 | // 2) restify-error context fields (restify-errors@ <= 5.x) 159 | // 3) verror info field 160 | var topLevelFields = (self._serializeTopLevelFields === true) ? 161 | _.omit(err, self._knownFields) : 162 | {}; 163 | 164 | // We don't want to load domains just to check if topLevelFields.domain is 165 | // a Domain instance, so first we make sure domains are already loaded. 166 | if (EventEmitter.usingDomains) { 167 | if (!domain) { 168 | // eslint-disable-next-line global-require 169 | domain = require('domain'); 170 | } 171 | 172 | if (topLevelFields.domain instanceof domain.Domain) { 173 | topLevelFields = _.omit(topLevelFields, [ 'domain' ]); 174 | } 175 | } 176 | 177 | // combine all fields into a pojo, and serialize 178 | var allFields = _.assign({}, topLevelFields, err.context, nerror.info(err)); 179 | 180 | if (!_.isEmpty(allFields)) { 181 | ret = ' (' + serializeIntoEqualString(allFields) + ')'; 182 | } 183 | 184 | return ret + '\n'; 185 | }; 186 | 187 | 188 | /** 189 | * find a list of known error fields that we don't want to serialize. create 190 | * verror instances to programatically build that list. 191 | * @private 192 | * @method _findKnownFields 193 | * @return {Array} 194 | */ 195 | ErrorSerializer.prototype._findKnownFields = function _findKnownFields() { 196 | // when looping through arbitrary fields attached to the error object, cross 197 | // reference them against this known list of fields. 198 | var fields = [ 199 | // known Error fields 200 | 'message', 201 | 'name', 202 | 'toJSON', 203 | // known restify-error fields 204 | 'toString', 205 | 'body' 206 | ]; 207 | 208 | // make a verror and multierror and find expected fields 209 | var verr = new VError(); 210 | var multiErr = new MultiError([ verr ]); 211 | fields.push(_.keys(verr)); 212 | fields.push(_.keys(Object.getPrototypeOf(verr))); 213 | fields.push(_.keys(multiErr)); 214 | fields.push(_.keys(Object.getPrototypeOf(multiErr))); 215 | 216 | return _(fields).flatten().uniq().value(); 217 | }; 218 | 219 | 220 | /** 221 | * built in bunyan serializer for restify errors. it's more or less the 222 | * standard bunyan serializer with support for the context property. 223 | * @private 224 | * @method serialize 225 | * @param {Object} err an error object 226 | * @returns {Object} serialized object for bunyan output 227 | */ 228 | ErrorSerializer.prototype.serialize = function serialize(err) { 229 | if (!err || !err.stack) { 230 | return err; 231 | } 232 | 233 | var self = this; 234 | var multiErr = (err.errors && _.isFunction(err.errors)); 235 | 236 | return { 237 | message: err.message, 238 | name: err.name, 239 | stack: (multiErr === true) ? 240 | self._getMultiErrorStack(err) : 241 | self._getFullErrorStack(err), 242 | code: err.code, 243 | signal: err.signal 244 | }; 245 | }; 246 | 247 | 248 | /** 249 | * copy pasta-ed from bunyan. 250 | * A JSON stringifier that handles cycles safely. 251 | * Usage: JSON.stringify(obj, safeCycles()) 252 | * @returns {Function} 253 | */ 254 | function safeCycles() { 255 | 256 | var seen = []; 257 | 258 | return function(key, val) { 259 | if (!val || typeof (val) !== 'object') { 260 | return val; 261 | } 262 | 263 | if (seen.indexOf(val) !== -1) { 264 | return '[Circular]'; 265 | } 266 | seen.push(val); 267 | return val; 268 | }; 269 | } 270 | 271 | 272 | /** 273 | * factory function to create customized serializers. 274 | * @public 275 | * @param {Object} options an options object 276 | * @return {Function} serializer function 277 | */ 278 | function factory(options) { 279 | assert.optionalObject(options, 'options'); 280 | 281 | var opts = _.assign({ 282 | topLevelFields: false 283 | }, options); 284 | 285 | var serializer = new ErrorSerializer(opts); 286 | // rebind the serialize function since this will be lost when we export it 287 | // as a POJO 288 | serializer.serialize = serializer.serialize.bind(serializer); 289 | 290 | return serializer; 291 | } 292 | 293 | 294 | // we should be exporting this create function, but to refrain from making it a 295 | // breaking change, let's attach the create to the existing function export. we 296 | // can make the change in next major version. 297 | var defaultSerializer = factory(); 298 | defaultSerializer.serialize.create = function create(opts) { 299 | var serializer = factory(opts); 300 | return { 301 | err: serializer.serialize 302 | }; 303 | }; 304 | 305 | 306 | module.exports = defaultSerializer.serialize; 307 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-errors", 3 | "version": "8.0.2", 4 | "main": "lib/index.js", 5 | "description": "Collection of Error objects shared across restify components.", 6 | "homepage": "http://www.restify.com", 7 | "author": { 8 | "name": "Alex Liu", 9 | "email": "donutespresso@gmail.com" 10 | }, 11 | "contributors": [ 12 | "Alex Liu", 13 | "Gergely Nemeth", 14 | "Mark Cavage" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/restify/errors.git" 19 | }, 20 | "license": "MIT", 21 | "files": [ 22 | "lib" 23 | ], 24 | "keywords": [ 25 | "restify-errors", 26 | "restify", 27 | "errors", 28 | "custom errors", 29 | "inherit errors", 30 | "http errors", 31 | "http status code", 32 | "rest errors" 33 | ], 34 | "scripts": { 35 | "test": "make test" 36 | }, 37 | "devDependencies": { 38 | "bunyan": "^1.8.12", 39 | "chai": "^4.2.0", 40 | "conventional-changelog-angular": "^5.0.6", 41 | "conventional-recommended-bump": "^6.0.5", 42 | "coveralls": "^3.0.9", 43 | "eslint": "^6.7.2", 44 | "mkdirp": "^0.5.1", 45 | "mocha": "^6.2.2", 46 | "nyc": "^14.1.1", 47 | "restify": "^8.5.0", 48 | "restify-clients": "^2.6.7", 49 | "unleash": "^2.0.1" 50 | }, 51 | "optionalDependencies": { 52 | "safe-json-stringify": "^1.2.0" 53 | }, 54 | "dependencies": { 55 | "@netflix/nerror": "^1.1.3", 56 | "assert-plus": "^1.0.0", 57 | "lodash": "^4.17.21" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-unused-expressions": [ 0 ], 7 | "require-jsdoc": [ 0 ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // jscs:disable maximumLineLength 2 | 3 | 'use strict'; 4 | 5 | 6 | // core modules 7 | var http = require('http'); 8 | 9 | // userland 10 | var assert = require('chai').assert; 11 | var bunyan = require('bunyan'); 12 | var _ = require('lodash'); 13 | var restify = require('restify'); 14 | var restifyClients = require('restify-clients'); 15 | var nerror = require('@netflix/nerror'); 16 | var WError = nerror.WError; 17 | var VError = nerror.VError; 18 | var MultiError = nerror.MultiError; 19 | 20 | // internal 21 | var helpers = require('../lib/helpers'); 22 | var HttpError = require('../lib/baseClasses/HttpError'); 23 | var RestError = require('../lib/baseClasses/RestError'); 24 | var httpErrors = require('../lib/httpErrors'); 25 | var restErrors = require('../lib/restErrors'); 26 | var restifyErrors = require('../lib/index.js'); 27 | 28 | 29 | 30 | describe('restify-errors node module.', function() { 31 | 32 | var ExecutionError; 33 | 34 | describe('HttpError class', function() { 35 | 36 | it('should create generic HttpError, inheriting from WError', 37 | function() { 38 | var myErr = new HttpError(); 39 | 40 | assert.equal(myErr instanceof HttpError, true); 41 | assert.equal(myErr instanceof WError, true); 42 | assert.equal(myErr instanceof Error, true); 43 | 44 | assert.equal(myErr.code, 'Error'); 45 | assert.deepEqual(JSON.stringify(myErr), JSON.stringify({ 46 | code: 'Error', 47 | message: '' 48 | })); 49 | }); 50 | 51 | it('should create HttpError using options object', function() { 52 | var options = { 53 | statusCode: 799, 54 | code: 'myhttp', 55 | info: { 56 | foo: 'bar', 57 | baz: [ 1, 2, 3 ] 58 | } 59 | }; 60 | var errMsg = 'my http error'; 61 | var myErr = new HttpError(options, errMsg); 62 | 63 | // verify properties on the error 64 | assert.equal(myErr.name, 'HttpError'); 65 | assert.equal(myErr.message, errMsg); 66 | assert.equal(myErr.statusCode, options.statusCode); 67 | assert.equal(myErr.code, 'myhttp'); 68 | assert.isObject(myErr.body); 69 | assert.equal(myErr.body.message, errMsg); 70 | assert.equal(myErr.body.code, 'myhttp'); 71 | assert.deepEqual(restifyErrors.info(myErr), options.info); 72 | }); 73 | 74 | it('should create HttpError and retain a prior cause', function() { 75 | // create http error with prior cause 76 | var priorErr = new Error('foobar'); 77 | var myErr = new HttpError(priorErr, 'new message'); 78 | 79 | assert.equal(myErr.cause(), priorErr); 80 | assert.equal(myErr.name, 'HttpError'); 81 | assert.equal(myErr.message, 'new message'); 82 | assert.isObject(myErr.body); 83 | assert.equal(myErr.body.message, 'new message'); 84 | assert.equal(myErr.body.code, 'Error'); 85 | 86 | // create http error with prior cause and options 87 | var myErr2Msg = 'bazbar'; 88 | var myErr2 = new HttpError({ 89 | cause: priorErr 90 | }, myErr2Msg); 91 | 92 | assert.equal(myErr2.cause(), priorErr); 93 | assert.equal(myErr2.name, 'HttpError'); 94 | assert.equal(myErr2.message, myErr2Msg); 95 | assert.isObject(myErr2.body); 96 | assert.equal(myErr2.body.message, myErr2Msg); 97 | assert.equal(myErr2.body.code, 'Error'); 98 | }); 99 | 100 | it('should create HttpError, args should fall through to WError', 101 | function() { 102 | var myErr = new HttpError('missing file: "%s"', 'foobar'); 103 | 104 | assert.equal(myErr.message, 'missing file: "foobar"'); 105 | }); 106 | 107 | it('should support .context property getter', function() { 108 | 109 | var info = { 110 | a: 1, 111 | b: 2 112 | }; 113 | var myErr = new HttpError({ 114 | info: info 115 | }, 'boom'); 116 | 117 | assert.deepEqual(nerror.info(myErr), info); 118 | assert.deepEqual(restifyErrors.info(myErr), info); 119 | assert.deepEqual(myErr.context, info); 120 | }); 121 | 122 | it('should override default toJSON and toString', function() { 123 | var myErr = new HttpError({ 124 | statusCode: 999, 125 | toString: function() { 126 | return 'boom'; 127 | }, 128 | toJSON: function() { 129 | var statusCode = this.statusCode; 130 | return { 131 | statusCode: statusCode 132 | }; 133 | } 134 | }, 'boom'); 135 | 136 | assert.strictEqual(myErr.toString(), 'boom'); 137 | assert.strictEqual( 138 | JSON.stringify(myErr), 139 | '{"statusCode":999}' 140 | ); 141 | }); 142 | }); 143 | 144 | describe('Built-in HttpError subclasses', function() { 145 | 146 | it('should create BadGatewayError, inheriting from HttpError from ' + 147 | 'WError', function() { 148 | var myErr = new httpErrors.BadGatewayError(); 149 | 150 | assert.equal(myErr instanceof httpErrors.BadGatewayError, true); 151 | assert.equal(myErr instanceof HttpError, true); 152 | assert.equal(myErr instanceof WError, true); 153 | assert.equal(myErr instanceof Error, true); 154 | 155 | // assert default status code 156 | assert.equal(myErr.code, 'BadGateway'); 157 | assert.equal(myErr.statusCode, 502); 158 | assert.equal(myErr.message, ''); 159 | 160 | // assert stringification 161 | assert.equal(JSON.stringify(myErr), JSON.stringify({ 162 | code: 'BadGateway', 163 | message: '' 164 | })); 165 | }); 166 | 167 | it('should create BadGatewayError using options object', function() { 168 | var msg = 'my http error'; 169 | var myErr = new httpErrors.BadGatewayError({ 170 | statusCode: 799, // can pass in any crazy status code 171 | code: 'myhttp', 172 | info: { 173 | foo: 'bar', 174 | baz: [ 1, 2, 3 ] 175 | } 176 | }, msg); 177 | 178 | assert.equal(myErr.name, 'BadGatewayError'); 179 | assert.equal(myErr.message, msg); 180 | assert.equal(myErr.statusCode, 799); 181 | assert.equal(myErr.code, 'myhttp'); 182 | assert.isObject(myErr.body); 183 | assert.equal(myErr.body.message, msg); 184 | assert.equal(myErr.body.code, 'myhttp'); 185 | assert.deepEqual(restifyErrors.info(myErr), { 186 | foo: 'bar', 187 | baz: [ 1, 2, 3 ] 188 | }); 189 | }); 190 | 191 | it('should create BadGatewayError, and retain a prior cause', 192 | function() { 193 | var priorErr = new Error('foobar'); 194 | var myErr = new httpErrors.BadGatewayError(priorErr); 195 | 196 | assert.equal(myErr.cause(), priorErr); 197 | assert.equal(myErr.name, 'BadGatewayError'); 198 | assert.equal(myErr.statusCode, 502); 199 | assert.isObject(myErr.body); 200 | assert.equal(myErr.body.message, ''); 201 | assert.equal(myErr.body.code, 'BadGateway'); 202 | 203 | var myErr2Msg = 'bazbar'; 204 | var myErr2 = new httpErrors.BadGatewayError({ 205 | cause: priorErr 206 | }, myErr2Msg); 207 | 208 | assert.equal(myErr.cause(), priorErr); 209 | assert.equal(myErr2.name, 'BadGatewayError'); 210 | assert.equal(myErr2.statusCode, 502); 211 | assert.equal(myErr2.message, myErr2Msg); 212 | assert.isObject(myErr2.body); 213 | assert.equal(myErr2.body.message, myErr2Msg); 214 | assert.equal(myErr2.body.code, 'BadGateway'); 215 | }); 216 | 217 | it('should create BadGatewayError, args should fall through to WError', 218 | function() { 219 | var myErr = new httpErrors.BadGatewayError( 220 | 'missing file: "%s"', 'foobar' 221 | ); 222 | 223 | assert.equal(myErr.message, 'missing file: "foobar"'); 224 | }); 225 | }); 226 | 227 | describe('RestError class', function() { 228 | it('should create generic RestError, inheriting from WError', 229 | function() { 230 | var myErr = new RestError(); 231 | 232 | assert.equal(myErr instanceof RestError, true); 233 | assert.equal(myErr instanceof WError, true); 234 | assert.equal(myErr instanceof Error, true); 235 | assert.equal(myErr.code, 'Error'); 236 | assert.equal(myErr.restCode, 'Error'); 237 | 238 | // assert stringification 239 | assert.equal(JSON.stringify(myErr), JSON.stringify({ 240 | code: 'Error', 241 | message: '' 242 | })); 243 | }); 244 | 245 | it('should create RestError, using options object', function() { 246 | var errMsg = 'my http error'; 247 | var options = { 248 | statusCode: 799, 249 | info: { 250 | foo: 'bar', 251 | baz: [ 1, 2, 3 ] 252 | } 253 | }; 254 | var myErr = new RestError(options, errMsg); 255 | 256 | // verify properties on the error 257 | assert.equal(myErr.name, 'RestError'); 258 | assert.equal(myErr.restCode, 'Error'); 259 | assert.equal(myErr.message, errMsg); 260 | assert.equal(myErr.statusCode, options.statusCode); 261 | assert.isObject(myErr.body); 262 | assert.equal(myErr.body.message, errMsg); 263 | assert.equal(myErr.body.code, 'Error'); 264 | assert.deepEqual(restifyErrors.info(myErr), { 265 | foo: 'bar', 266 | baz: [ 1, 2, 3 ] 267 | }); 268 | }); 269 | 270 | it('should create RestError, and retain a prior cause', function() { 271 | // create http error with prior cause 272 | var priorErr = new Error('foobar'); 273 | var myErr = new RestError(priorErr); 274 | 275 | assert.equal(myErr.cause(), priorErr); 276 | assert.equal(myErr.name, 'RestError'); 277 | assert.equal(myErr.restCode, 'Error'); 278 | assert.equal(myErr.message, ''); 279 | assert.isObject(myErr.body); 280 | assert.equal(myErr.body.message, ''); 281 | assert.equal(myErr.body.code, 'Error'); 282 | 283 | // create http error with prior cause and options 284 | var errMsg = 'bazbar'; 285 | var options = { 286 | cause: priorErr, 287 | restCode: 'yay' 288 | }; 289 | var myErr2 = new RestError(options, errMsg); 290 | 291 | assert.equal(myErr2.cause(), priorErr); 292 | assert.equal(myErr2.name, 'RestError'); 293 | assert.equal(myErr2.restCode, options.restCode); 294 | assert.equal(myErr2.message, errMsg); 295 | assert.isObject(myErr2.body); 296 | assert.equal(myErr2.body.message, errMsg); 297 | assert.equal(myErr2.body.code, 'yay'); 298 | }); 299 | 300 | it('should create RestError, args should fall through to WError', 301 | function() { 302 | var myErr = new RestError('missing file: "%s"', 'foobar'); 303 | 304 | assert.equal(myErr.message, 'missing file: "foobar"'); 305 | }); 306 | }); 307 | 308 | describe('Built-in RestError subclasses', function() { 309 | 310 | it('should create BadDigestError, inheriting from RestError' + 311 | '/HttpError/WError', function() { 312 | var myErr = new restErrors.BadDigestError(); 313 | 314 | assert.equal(myErr instanceof restErrors.BadDigestError, true); 315 | assert.equal(myErr instanceof RestError, true); 316 | assert.equal(myErr instanceof HttpError, true); 317 | assert.equal(myErr instanceof WError, true); 318 | assert.equal(myErr instanceof Error, true); 319 | assert.equal(myErr.code, 'Error'); 320 | assert.equal(myErr.restCode, 'BadDigest'); 321 | 322 | // assert stringification 323 | assert.equal(JSON.stringify(myErr), JSON.stringify({ 324 | code: 'BadDigest', 325 | message: '' 326 | })); 327 | }); 328 | 329 | it('should create BadDigestError, using options object', function() { 330 | var options = { 331 | restCode: 'yay', 332 | statusCode: 799, 333 | info: { 334 | foo: 'bar', 335 | baz: [ 1, 2, 3 ] 336 | } 337 | }; 338 | var errMsg = 'my http error'; 339 | var myErr = new restErrors.BadDigestError(options, errMsg); 340 | 341 | // verify properties on the error 342 | assert.equal(myErr.name, 'BadDigestError'); 343 | assert.equal(myErr.restCode, options.restCode); 344 | assert.equal(myErr.message, errMsg); 345 | assert.equal(myErr.statusCode, options.statusCode); 346 | assert.isObject(myErr.body); 347 | assert.equal(myErr.body.message, errMsg); 348 | assert.equal(myErr.body.code, 'yay'); 349 | assert.deepEqual(restifyErrors.info(myErr), { 350 | foo: 'bar', 351 | baz: [ 1, 2, 3 ] 352 | }); 353 | }); 354 | 355 | it('should create BadDigestError, args should fall through to WError', 356 | function() { 357 | var myErr = new restErrors.BadDigestError( 358 | 'missing file: "%s"', 'foobar' 359 | ); 360 | 361 | assert.equal(myErr.name, 'BadDigestError'); 362 | assert.equal(myErr.restCode, 'BadDigest'); 363 | assert.equal(myErr.statusCode, 400); 364 | assert.isObject(myErr.body); 365 | assert.equal(myErr.body.message, 'missing file: "foobar"'); 366 | assert.equal(myErr.body.code, 'BadDigest'); 367 | }); 368 | 369 | it('should create BadDigestError using options, should prefer ' + 370 | 'printf over options', function() { 371 | var myErr = new restErrors.BadDigestError({ 372 | restCode: 'Bad Digestion', 373 | message: 'this error should not match' 374 | }, 'missing file: "%s"', 'foobar'); 375 | 376 | assert.equal(myErr.name, 'BadDigestError'); 377 | assert.equal(myErr.restCode, 'Bad Digestion'); 378 | assert.equal(myErr.statusCode, 400); 379 | assert.isObject(myErr.body); 380 | assert.equal(myErr.body.message, 'missing file: "foobar"'); 381 | assert.equal(myErr.body.code, 'Bad Digestion'); 382 | }); 383 | }); 384 | 385 | describe('helpers', function() { 386 | 387 | function parse() { 388 | return helpers.parseVErrorArgs(arguments); 389 | } 390 | 391 | it('should parse VError arguments with options object', function() { 392 | 393 | var options = { 394 | statusCode: 101 395 | }; 396 | var parsed = parse(options); 397 | 398 | assert.deepEqual(parsed, { 399 | verrorArgs: [{}], 400 | internalOpts: { 401 | statusCode: 101 402 | } 403 | }); 404 | }); 405 | 406 | it('should parse VError arguments with strings (pass through to ' + 407 | 'WError)', function() { 408 | 409 | var parsed = parse('missing file: "%s"', 'foobar'); 410 | 411 | assert.deepEqual(parsed, { 412 | verrorArgs: [ 'missing file: "%s"', 'foobar' ], 413 | internalOpts: {} 414 | }); 415 | }); 416 | 417 | it('should parse variadic arguments with priorCause and strings ' + 418 | '(pass through to WError)', function() { 419 | 420 | var err = new Error('foobar'); 421 | var parsed = parse(err, 'a', 'b', 'c'); 422 | 423 | assert.deepEqual(parsed, { 424 | verrorArgs: [ err, 'a', 'b', 'c' ], 425 | internalOpts: {} 426 | }); 427 | }); 428 | }); 429 | 430 | describe('stack trace cleanliness', function() { 431 | it('should have test file as first line of HttpError stack trace', 432 | function testStack1() { 433 | var httpErr = new HttpError('http error'); 434 | var stack = httpErr.stack.split('\n'); 435 | 436 | // ensure stack trace's first line is the test file. 437 | assert.equal(_.includes(stack[0], 'HttpError: http error'), true); 438 | assert.equal(_.includes(stack[1], 'Context.testStack1'), true); 439 | assert.equal(_.includes(stack[1], 'test/index.js'), true); 440 | }); 441 | 442 | it('should have test file as first line of built-in HttpError ' + 443 | 'stack trace', function testStack2() { 444 | // test built in http errors 445 | var badReqErr = new httpErrors.BadRequestError('i am bad'); 446 | var stack = badReqErr.stack.split('\n'); 447 | 448 | assert.equal( 449 | _.includes(stack[0], 'BadRequestError: i am bad'), 450 | true 451 | ); 452 | assert.equal(_.includes(stack[1], 'Context.testStack2'), true); 453 | assert.equal(_.includes(stack[1], 'test/index.js'), true); 454 | }); 455 | 456 | it('should have test file as first line in RestError stack trace', 457 | function testStack3() { 458 | // test built in http errors 459 | var restErr = new RestError('i am rest'); 460 | var stack = restErr.stack.split('\n'); 461 | 462 | assert.equal(_.includes(stack[0], 'RestError: i am rest'), true); 463 | assert.equal(_.includes(stack[1], 'Context.testStack3'), true); 464 | assert.equal(_.includes(stack[1], 'test/index.js'), true); 465 | }); 466 | 467 | it('should have test file as first line of built-in RestError stack ' + 468 | 'trace', function testStack4() { 469 | // test built in http errors 470 | var badDigestError = new restErrors.BadDigestError('indigestion'); 471 | var stack = badDigestError.stack.split('\n'); 472 | 473 | assert.equal( 474 | _.includes(stack[0], 'BadDigestError: indigestion'), 475 | true 476 | ); 477 | assert.equal(_.includes(stack[1], 'Context.testStack4'), true); 478 | assert.equal(_.includes(stack[1], 'test/index.js'), true); 479 | }); 480 | 481 | it('should have test file as first line of subclass error stack trace', 482 | function testStack5() { 483 | var ChargeError = restifyErrors.makeConstructor('ChargeError'); 484 | var err = new ChargeError('did not charge long enough'); 485 | var stack = err.stack.split('\n'); 486 | 487 | assert.equal( 488 | _.includes(stack[0], 'ChargeError: did not charge long enough'), 489 | true 490 | ); 491 | assert.equal(_.includes(stack[1], 'Context.testStack5'), true); 492 | assert.equal(_.includes(stack[1], 'test/index.js'), true); 493 | }); 494 | 495 | it('should have test file as first line of stack trace for error ' + 496 | 'created via makeErrFromCode', function testStack6() { 497 | var err = restifyErrors.makeErrFromCode(401, 'no creds'); 498 | var stack = err.stack.split('\n'); 499 | 500 | assert.equal( 501 | _.includes(stack[0], 'UnauthorizedError: no creds'), 502 | true 503 | ); 504 | assert.equal(_.includes(stack[1], 'Context.testStack6'), true); 505 | assert.equal(_.includes(stack[1], 'test/index.js'), true); 506 | }); 507 | }); 508 | 509 | describe('main exports', function() { 510 | 511 | it('should export a constructor for every http error code (400-500)', 512 | function() { 513 | // ensure we have the same amount of errors for every http error 514 | // exposed on the core http module. we only care about http status 515 | // codes 400-500. 516 | var numRawErrors = _.filter( 517 | http.STATUS_CODES, 518 | function(desc, code) { 519 | return parseInt(code, 10) >= 400; 520 | } 521 | ).length; 522 | 523 | assert.equal( 524 | numRawErrors, 525 | _.size(httpErrors) 526 | ); 527 | 528 | // ensure each one has a displayName that ends in 'Error' 529 | // then try to new up one of each. 530 | _.forEach(httpErrors, function(ErrCtor) { 531 | assert.isString(ErrCtor.displayName); 532 | assert.equal(_.endsWith(ErrCtor.displayName, 'Error'), true); 533 | 534 | var err = new ErrCtor(); 535 | assert.equal(err instanceof Error, true); 536 | }); 537 | }); 538 | 539 | it('should export a constructor for every built-in RestError type', 540 | function() { 541 | // no good way to verify we got all the constructors, so it's hard 542 | // coded for now. 543 | // 16 built-in RestError subclasses 544 | assert.equal(_.size(restErrors), 15); 545 | 546 | // ensure each one has a displayName that ends in 'Error' 547 | // then try to new up one of each. 548 | _.forEach(httpErrors, function(ErrCtor) { 549 | assert.isString(ErrCtor.displayName); 550 | assert.equal(_.endsWith(ErrCtor.displayName, 'Error'), true); 551 | 552 | var err = new ErrCtor(); 553 | assert.equal(err instanceof Error, true); 554 | }); 555 | }); 556 | 557 | it('should export roll up of all constructors', function() { 558 | assert.isObject(restifyErrors); 559 | // again, no way to know since we programatically create errors, 560 | // but ensure at least we have all RestErrors 561 | assert.isAbove(_.size(restifyErrors), 30); 562 | }); 563 | 564 | it('should have code properties for all HttpError constructors', 565 | function() { 566 | _.forEach(httpErrors, function(HttpErr) { 567 | var err = new HttpErr(); 568 | // strip off the last 5 chars ('Error') and do an assertion 569 | assert.equal(err.code, HttpErr.displayName.slice(0, -5)); 570 | }); 571 | }); 572 | 573 | it('should have restCode properties for all RestError constructors', 574 | function() { 575 | _.forEach(restErrors, function(RestErr) { 576 | var err = new RestErr(); 577 | // strip off the last 5 chars ('Error') and do an assertion 578 | assert.equal(err.restCode, RestErr.displayName.slice(0, -5)); 579 | }); 580 | }); 581 | 582 | it('should create custom error using makeConstructor', function() { 583 | ExecutionError = restifyErrors.makeConstructor('ExecutionError', { 584 | statusCode: 406, 585 | failureType: 'motion', 586 | code: 'moo', 587 | message: 'Default Execution Error' 588 | }); 589 | }); 590 | 591 | it('should create custom error instance', function() { 592 | var underlyingErr = new Error('underlying error!'); 593 | var err = new ExecutionError(underlyingErr, 'bad joystick input'); 594 | 595 | assert.equal(err instanceof ExecutionError, true); 596 | assert.equal(err instanceof RestError, true); 597 | assert.equal(err instanceof HttpError, true); 598 | assert.equal(err instanceof WError, true); 599 | assert.equal(err instanceof Error, true); 600 | assert.equal(err.message, 'bad joystick input'); 601 | assert.equal(err.statusCode, 406); 602 | assert.equal(err.failureType, 'motion'); 603 | assert.equal(err.restCode, 'Execution'); 604 | assert.equal(err.code, 'moo'); 605 | assert.isObject(err.body); 606 | assert.equal(err.body.code, 'Execution'); 607 | assert.equal(err.body.message, 'bad joystick input'); 608 | assert.equal(err.cause(), underlyingErr); 609 | 610 | // assert stringification 611 | var expectedJSON = { 612 | code: 'Execution', 613 | message: 'bad joystick input; ' + 614 | 'caused by Error: underlying error!' 615 | }; 616 | assert.equal(JSON.stringify(err), JSON.stringify(expectedJSON)); 617 | assert.equal( 618 | err.toString(), 619 | err.name + ': ' + expectedJSON.message 620 | ); 621 | }); 622 | 623 | it('should create custom error instance using options', function() { 624 | var underlyingErr = new Error('underlying error!'); 625 | var options = { 626 | cause: underlyingErr, 627 | statusCode: 799, 628 | info: { 629 | foo: 'bar', 630 | baz: [ 1, 2, 3 ] 631 | } 632 | }; 633 | var errMsg = 'bad joystick input'; 634 | var err = new ExecutionError(options, errMsg); 635 | 636 | assert.equal(err instanceof ExecutionError, true); 637 | assert.equal(err instanceof RestError, true); 638 | assert.equal(err instanceof HttpError, true); 639 | assert.equal(err instanceof WError, true); 640 | assert.equal(err instanceof Error, true); 641 | assert.equal(err.message, 'bad joystick input'); 642 | assert.equal(err.statusCode, 799); 643 | assert.equal(err.failureType, 'motion'); 644 | assert.equal(err.restCode, 'Execution'); 645 | assert.equal(err.code, 'moo'); 646 | assert.isObject(err.body); 647 | assert.equal(err.body.code, 'Execution'); 648 | assert.equal(err.body.message, 'bad joystick input'); 649 | assert.equal(err.cause(), underlyingErr); 650 | assert.deepEqual(restifyErrors.info(err), { 651 | foo: 'bar', 652 | baz: [ 1, 2, 3 ] 653 | }); 654 | 655 | // assert stringification 656 | var expectedJSON = { 657 | code: 'Execution', 658 | message: 'bad joystick input; ' + 659 | 'caused by Error: underlying error!' 660 | }; 661 | assert.equal(JSON.stringify(err), JSON.stringify(expectedJSON)); 662 | assert.equal( 663 | err.toString(), 664 | err.name + ': ' + expectedJSON.message 665 | ); 666 | }); 667 | 668 | it('should create custom error using makeConstructor (with lower ' + 669 | 'case Error name)', function() { 670 | var underlyingErr = new Error('underlying error!'); 671 | var Executionerror = restifyErrors.makeConstructor( 672 | 'Executionerror', 673 | { 674 | statusCode: 406, 675 | failureType: 'motion', 676 | code: 'moo' 677 | } 678 | ); 679 | var err = new Executionerror(underlyingErr, 'bad joystick input'); 680 | 681 | assert.equal(err instanceof Executionerror, true); 682 | 683 | // assert stringification 684 | var expectedJSON = { 685 | code: 'Execution', 686 | message: 'bad joystick input; ' + 687 | 'caused by Error: underlying error!' 688 | }; 689 | assert.equal(JSON.stringify(err), JSON.stringify(expectedJSON)); 690 | assert.equal( 691 | err.toString(), 692 | err.name + ': ' + expectedJSON.message 693 | ); 694 | }); 695 | 696 | it('should have message property fallback to custom error options', 697 | function() { 698 | var err = new ExecutionError('printf-style %s', 'error'); 699 | assert.equal(err.message, 'printf-style error'); 700 | 701 | err = new ExecutionError({ 702 | info: { 703 | foo: 'bar' 704 | } 705 | }, 'printf-style %s', 'error'); 706 | assert.equal(err.message, 'printf-style error'); 707 | assert.deepEqual(restifyErrors.info(err), { 708 | foo: 'bar' 709 | }); 710 | 711 | err = new ExecutionError(); 712 | assert.equal(err.message, 'Default Execution Error'); 713 | 714 | // assert fallback to empty string if no message provided 715 | var NoDefaultMessageError = restifyErrors.makeConstructor( 716 | 'NoDefaultMessageError' 717 | ); 718 | err = new NoDefaultMessageError(); 719 | assert.equal(err.message, ''); 720 | }); 721 | 722 | it('should create an error from an http status code', function() { 723 | var err = restifyErrors.makeErrFromCode(406, 'the horror'); 724 | 725 | assert.equal( 726 | err instanceof restifyErrors.NotAcceptableError, 727 | true 728 | ); 729 | assert.equal(err instanceof HttpError, true); 730 | assert.equal(err instanceof WError, true); 731 | assert.equal(err instanceof Error, true); 732 | assert.equal(err.message, 'the horror'); 733 | assert.equal(err.statusCode, 406); 734 | assert.isObject(err.body); 735 | assert.equal(err.body.code, 'NotAcceptable'); 736 | assert.equal(err.body.message, 'the horror'); 737 | 738 | // assert stringification 739 | assert.equal(JSON.stringify(err), JSON.stringify({ 740 | code: 'NotAcceptable', 741 | message: 'the horror' 742 | })); 743 | }); 744 | }); 745 | 746 | describe('restify integration', function() { 747 | 748 | var server; 749 | var client; 750 | 751 | before(function(done) { 752 | server = restify.createServer({ 753 | name: 'restifyErrors' 754 | }); 755 | client = restifyClients.createJSONClient({ 756 | url: 'http://localhost:3000' 757 | }); 758 | server.listen(3000, done); 759 | }); 760 | 761 | after(function(done) { 762 | client.close(); 763 | server.close(done); 764 | }); 765 | 766 | it('should send HttpErrors with status codes', function(done) { 767 | server.get('/1', function(req, res, next) { 768 | res.send(new restifyErrors.NotFoundError('gone girl')); 769 | next(); 770 | }); 771 | 772 | client.get('/1', function(err, req, res, data) { 773 | assert.ok(err); 774 | assert.equal(res.statusCode, 404); 775 | assert.equal(data.message, 'gone girl'); 776 | done(); 777 | }); 778 | }); 779 | 780 | it('should send RestErrors with status codes', function(done) { 781 | server.get('/2', function(req, res, next) { 782 | res.send(new restifyErrors.BadDigestError('indigestion')); 783 | next(); 784 | }); 785 | 786 | client.get('/2', function(err, req, res, data) { 787 | assert.ok(err); 788 | assert.equal(res.statusCode, 400); 789 | assert.equal(data.message, 'indigestion'); 790 | done(); 791 | }); 792 | }); 793 | 794 | it('should send custom errors with status codes', function(done) { 795 | server.get('/3', function(req, res, next) { 796 | res.send(new ExecutionError('bad joystick input!')); 797 | next(); 798 | }); 799 | 800 | client.get('/3', function(err, req, res, data) { 801 | assert.ok(err); 802 | assert.equal(res.statusCode, 406); 803 | assert.equal(data.message, 'bad joystick input!'); 804 | done(); 805 | }); 806 | }); 807 | }); 808 | 809 | describe('bunyan serializer', function() { 810 | 811 | var logger; 812 | 813 | it('should expose default serializer function', function() { 814 | var serializer = restifyErrors.bunyanSerializer; 815 | assert.isFunction(serializer); 816 | }); 817 | 818 | it('should expose factory for serializer creation', function() { 819 | var create = restifyErrors.bunyanSerializer.create; 820 | assert.isFunction(create); 821 | }); 822 | 823 | it('should create a custom bucket of err serializer', function() { 824 | // the factory returns a POJO of serializer keys to serializer 825 | // functions. 826 | var serializer = restifyErrors.bunyanSerializer.create(); 827 | // returned serializer object is of form: 828 | // { 829 | // err: 830 | // } 831 | // which matches bunyan and pino 832 | assert.isObject(serializer); 833 | assert.deepEqual(_.keys(serializer), [ 'err' ]); 834 | assert.isFunction(serializer.err); 835 | }); 836 | 837 | it('should create bunyan logger with serializer', function() { 838 | logger = bunyan.createLogger({ 839 | name: 'unit-test', 840 | serializers: { 841 | err: restifyErrors.bunyanSerializer 842 | } 843 | }); 844 | }); 845 | 846 | it('should serialize a standard Error', function(done) { 847 | 848 | var err = new Error('boom'); 849 | 850 | assert.doesNotThrow(function() { 851 | logger.error(err, 'standard error'); 852 | }); 853 | 854 | done(); 855 | }); 856 | 857 | it('should ignore serializer', function(done) { 858 | 859 | // pass an error object without stack 860 | assert.doesNotThrow(function() { 861 | logger.error({ 862 | err: null 863 | }, 'wrapped error'); 864 | }); 865 | 866 | assert.doesNotThrow(function() { 867 | logger.error({ 868 | err: {} 869 | }, 'wrapped error'); 870 | }); 871 | 872 | done(); 873 | }); 874 | 875 | it('should handle circular refs', function(done) { 876 | 877 | var a = {}; 878 | var b = { foo: a }; 879 | a.foo = b; 880 | 881 | var err = new RestError({ 882 | message: 'boom', 883 | info: a 884 | }); 885 | 886 | assert.doesNotThrow(function() { 887 | logger.error({ 888 | err: err 889 | }, 'wrapped error'); 890 | }); 891 | 892 | done(); 893 | }); 894 | 895 | it('should serialize a VError with info', function() { 896 | 897 | var err = new VError({ 898 | name: 'VErrorInfo', 899 | info: { 900 | foo: 'qux', 901 | baz: 2 902 | } 903 | }, 'this is a verror with info'); 904 | 905 | assert.doesNotThrow(function() { 906 | logger.error(err); 907 | }); 908 | }); 909 | 910 | it('should serialize a MultiError', function() { 911 | 912 | var err1 = new Error('boom'); 913 | var err2 = new restifyErrors.InternalServerError({ 914 | cause: err1, 915 | info: { 916 | foo: 'bar', 917 | baz: 1 918 | } 919 | }, 'ISE'); 920 | var err3 = new VError({ 921 | name: 'VErrorInfo', 922 | cause: err1, 923 | info: { 924 | foo: 'qux', 925 | baz: 2 926 | } 927 | }, 'this is a verror with info'); 928 | var multiError = new MultiError([ err1, err2, err3 ]); 929 | 930 | assert.doesNotThrow(function() { 931 | logger.error(multiError, 'MultiError'); 932 | }); 933 | }); 934 | 935 | it('should not serialize arbitrary fields on VError', function() { 936 | var err1 = new VError({ 937 | name: 'VErrorInfo', 938 | info: { 939 | espresso: 'ristretto' 940 | } 941 | }, 'pull!'); 942 | err1.espresso = 'lungo'; 943 | 944 | assert.doesNotThrow(function() { 945 | logger.error(err1, 'verror with fields'); 946 | }); 947 | }); 948 | 949 | it('should not serialize arbitrary fields on Error', function() { 950 | var serializer = restifyErrors.bunyanSerializer; 951 | var err1 = new Error('pull!'); 952 | err1.espresso = 'normale'; 953 | 954 | var serializedErr = serializer(err1); 955 | assert.notInclude(serializedErr.stack, 'espresso=normale'); 956 | }); 957 | 958 | it('should serialize arbitrary fields on Error', function() { 959 | var serializer = restifyErrors.bunyanSerializer.create({ 960 | topLevelFields: true 961 | }); 962 | var err1 = new Error('pull!'); 963 | err1.espresso = 'normale'; 964 | 965 | logger.child({ serializers: serializer }).error(err1); 966 | 967 | var serializedErr = serializer.err(err1, 'oh noes!'); 968 | assert.notInclude(serializedErr.stack, 'espresso=normale'); 969 | }); 970 | 971 | it('should not serialize known fields on VError', function() { 972 | var serializer = restifyErrors.bunyanSerializer.create({ 973 | topLevelFields: true 974 | }); 975 | var err1 = new VError({ 976 | name: 'VErrorInfo', 977 | info: { 978 | espresso: 'ristretto' 979 | } 980 | }, 'pull!'); 981 | err1.espresso = 'lungo'; 982 | 983 | logger.child({ serializers: serializer }).error(err1); 984 | 985 | var serializedErr = serializer.err(err1, 'oh noes!'); 986 | assert.notInclude(serializedErr.stack, 'cause=undefined'); 987 | }); 988 | 989 | it('should serialize domain when not using node domains', function() { 990 | var serializer = restifyErrors.bunyanSerializer.create({ 991 | topLevelFields: true 992 | }); 993 | var err1 = new Error('foo'); 994 | err1.domain = 'bar'; 995 | 996 | logger.child({ serializers: serializer }).error(err1); 997 | 998 | var serializedErr = serializer.err(err1, 'oh noes!'); 999 | assert.include(serializedErr.stack, 'domain="bar"'); 1000 | }); 1001 | 1002 | // eslint-disable-next-line max-len 1003 | it('should not serialize domain when using node domains', function(done) { 1004 | // eslint-disable-next-line global-require 1005 | var domain = require('domain'); 1006 | var dom = domain.create(); 1007 | dom.on('error', function(err1) { 1008 | var serializer = restifyErrors.bunyanSerializer.create({ 1009 | topLevelFields: true 1010 | }); 1011 | 1012 | logger.child({ serializers: serializer }).error(err1); 1013 | 1014 | var serializedErr = serializer.err(err1, 'oh noes!'); 1015 | // This weird pattern is necessary to make mocha report failures 1016 | // correctly inside a domain context 1017 | try { 1018 | assert.notInclude(serializedErr.stack, 'domain='); 1019 | return done(); 1020 | } catch (e) { 1021 | return done(e); 1022 | } 1023 | }); 1024 | dom.run(function() { 1025 | process.nextTick(function() { 1026 | throw new Error('foo'); 1027 | }); 1028 | }); 1029 | }); 1030 | }); 1031 | }); 1032 | -------------------------------------------------------------------------------- /tools/githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # commit hook for JShint 4 | make prepush 5 | prepushProcess=$? 6 | 7 | # now output the stuff 8 | exit $prepushProcess 9 | --------------------------------------------------------------------------------