├── .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 | [](https://npmjs.org/package/restify-errors)
4 | [](https://travis-ci.org/restify/errors)
5 | [](https://coveralls.io/r/restify/errors?branch=master)
6 | [](https://david-dm.org/restify/errors)
7 | [](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 |
--------------------------------------------------------------------------------