UNPKG

12.4 kBJavaScriptView Raw
1'use strict';
2
3const Hoek = require('@hapi/hoek');
4
5
6const internals = {
7 codes: new Map([
8 [100, 'Continue'],
9 [101, 'Switching Protocols'],
10 [102, 'Processing'],
11 [200, 'OK'],
12 [201, 'Created'],
13 [202, 'Accepted'],
14 [203, 'Non-Authoritative Information'],
15 [204, 'No Content'],
16 [205, 'Reset Content'],
17 [206, 'Partial Content'],
18 [207, 'Multi-Status'],
19 [300, 'Multiple Choices'],
20 [301, 'Moved Permanently'],
21 [302, 'Moved Temporarily'],
22 [303, 'See Other'],
23 [304, 'Not Modified'],
24 [305, 'Use Proxy'],
25 [307, 'Temporary Redirect'],
26 [400, 'Bad Request'],
27 [401, 'Unauthorized'],
28 [402, 'Payment Required'],
29 [403, 'Forbidden'],
30 [404, 'Not Found'],
31 [405, 'Method Not Allowed'],
32 [406, 'Not Acceptable'],
33 [407, 'Proxy Authentication Required'],
34 [408, 'Request Time-out'],
35 [409, 'Conflict'],
36 [410, 'Gone'],
37 [411, 'Length Required'],
38 [412, 'Precondition Failed'],
39 [413, 'Request Entity Too Large'],
40 [414, 'Request-URI Too Large'],
41 [415, 'Unsupported Media Type'],
42 [416, 'Requested Range Not Satisfiable'],
43 [417, 'Expectation Failed'],
44 [418, 'I\'m a teapot'],
45 [422, 'Unprocessable Entity'],
46 [423, 'Locked'],
47 [424, 'Failed Dependency'],
48 [425, 'Too Early'],
49 [426, 'Upgrade Required'],
50 [428, 'Precondition Required'],
51 [429, 'Too Many Requests'],
52 [431, 'Request Header Fields Too Large'],
53 [451, 'Unavailable For Legal Reasons'],
54 [500, 'Internal Server Error'],
55 [501, 'Not Implemented'],
56 [502, 'Bad Gateway'],
57 [503, 'Service Unavailable'],
58 [504, 'Gateway Time-out'],
59 [505, 'HTTP Version Not Supported'],
60 [506, 'Variant Also Negotiates'],
61 [507, 'Insufficient Storage'],
62 [509, 'Bandwidth Limit Exceeded'],
63 [510, 'Not Extended'],
64 [511, 'Network Authentication Required']
65 ])
66};
67
68
69exports.Boom = class extends Error {
70
71 constructor(message, options = {}) {
72
73 if (message instanceof Error) {
74 return exports.boomify(Hoek.clone(message), options);
75 }
76
77 const { statusCode = 500, data = null, ctor = exports.Boom } = options;
78 const error = new Error(message ? message : undefined); // Avoids settings null message
79 Error.captureStackTrace(error, ctor); // Filter the stack to our external API
80 error.data = data;
81 const boom = internals.initialize(error, statusCode);
82
83 Object.defineProperty(boom, 'typeof', { value: ctor });
84
85 if (options.decorate) {
86 Object.assign(boom, options.decorate);
87 }
88
89 return boom;
90 }
91
92 static [Symbol.hasInstance](instance) {
93
94 if (this === exports.Boom) {
95 return exports.isBoom(instance);
96 }
97
98 // Cannot use 'instanceof' as it creates infinite recursion
99
100 return this.prototype.isPrototypeOf(instance);
101 }
102};
103
104
105exports.isBoom = function (err, statusCode) {
106
107 return err instanceof Error && !!err.isBoom && (!statusCode || err.output.statusCode === statusCode);
108};
109
110
111exports.boomify = function (err, options) {
112
113 Hoek.assert(err instanceof Error, 'Cannot wrap non-Error object');
114
115 options = options || {};
116
117 if (options.data !== undefined) {
118 err.data = options.data;
119 }
120
121 if (options.decorate) {
122 Object.assign(err, options.decorate);
123 }
124
125 if (!err.isBoom) {
126 return internals.initialize(err, options.statusCode || 500, options.message);
127 }
128
129 if (options.override === false || // Defaults to true
130 !options.statusCode && !options.message) {
131
132 return err;
133 }
134
135 return internals.initialize(err, options.statusCode || err.output.statusCode, options.message);
136};
137
138
139// 4xx Client Errors
140
141exports.badRequest = function (message, data) {
142
143 return new exports.Boom(message, { statusCode: 400, data, ctor: exports.badRequest });
144};
145
146
147exports.unauthorized = function (message, scheme, attributes) { // Or (message, wwwAuthenticate[])
148
149 const err = new exports.Boom(message, { statusCode: 401, ctor: exports.unauthorized });
150
151 // function (message)
152
153 if (!scheme) {
154 return err;
155 }
156
157 // function (message, wwwAuthenticate[])
158
159 if (typeof scheme !== 'string') {
160 err.output.headers['WWW-Authenticate'] = scheme.join(', ');
161 return err;
162 }
163
164 // function (message, scheme, attributes)
165
166 let wwwAuthenticate = `${scheme}`;
167
168 if (attributes ||
169 message) {
170
171 err.output.payload.attributes = {};
172 }
173
174 if (attributes) {
175 if (typeof attributes === 'string') {
176 wwwAuthenticate += ' ' + Hoek.escapeHeaderAttribute(attributes);
177 err.output.payload.attributes = attributes;
178 }
179 else {
180 wwwAuthenticate += ' ' + Object.keys(attributes).map((name) => {
181
182 let value = attributes[name];
183 if (value === null ||
184 value === undefined) {
185
186 value = '';
187 }
188
189 err.output.payload.attributes[name] = value;
190 return `${name}="${Hoek.escapeHeaderAttribute(value.toString())}"`;
191 })
192 .join(', ');
193 }
194 }
195
196 if (message) {
197 if (attributes) {
198 wwwAuthenticate += ',';
199 }
200
201 wwwAuthenticate += ` error="${Hoek.escapeHeaderAttribute(message)}"`;
202 err.output.payload.attributes.error = message;
203 }
204 else {
205 err.isMissing = true;
206 }
207
208 err.output.headers['WWW-Authenticate'] = wwwAuthenticate;
209 return err;
210};
211
212
213exports.paymentRequired = function (message, data) {
214
215 return new exports.Boom(message, { statusCode: 402, data, ctor: exports.paymentRequired });
216};
217
218
219exports.forbidden = function (message, data) {
220
221 return new exports.Boom(message, { statusCode: 403, data, ctor: exports.forbidden });
222};
223
224
225exports.notFound = function (message, data) {
226
227 return new exports.Boom(message, { statusCode: 404, data, ctor: exports.notFound });
228};
229
230
231exports.methodNotAllowed = function (message, data, allow) {
232
233 const err = new exports.Boom(message, { statusCode: 405, data, ctor: exports.methodNotAllowed });
234
235 if (typeof allow === 'string') {
236 allow = [allow];
237 }
238
239 if (Array.isArray(allow)) {
240 err.output.headers.Allow = allow.join(', ');
241 }
242
243 return err;
244};
245
246
247exports.notAcceptable = function (message, data) {
248
249 return new exports.Boom(message, { statusCode: 406, data, ctor: exports.notAcceptable });
250};
251
252
253exports.proxyAuthRequired = function (message, data) {
254
255 return new exports.Boom(message, { statusCode: 407, data, ctor: exports.proxyAuthRequired });
256};
257
258
259exports.clientTimeout = function (message, data) {
260
261 return new exports.Boom(message, { statusCode: 408, data, ctor: exports.clientTimeout });
262};
263
264
265exports.conflict = function (message, data) {
266
267 return new exports.Boom(message, { statusCode: 409, data, ctor: exports.conflict });
268};
269
270
271exports.resourceGone = function (message, data) {
272
273 return new exports.Boom(message, { statusCode: 410, data, ctor: exports.resourceGone });
274};
275
276
277exports.lengthRequired = function (message, data) {
278
279 return new exports.Boom(message, { statusCode: 411, data, ctor: exports.lengthRequired });
280};
281
282
283exports.preconditionFailed = function (message, data) {
284
285 return new exports.Boom(message, { statusCode: 412, data, ctor: exports.preconditionFailed });
286};
287
288
289exports.entityTooLarge = function (message, data) {
290
291 return new exports.Boom(message, { statusCode: 413, data, ctor: exports.entityTooLarge });
292};
293
294
295exports.uriTooLong = function (message, data) {
296
297 return new exports.Boom(message, { statusCode: 414, data, ctor: exports.uriTooLong });
298};
299
300
301exports.unsupportedMediaType = function (message, data) {
302
303 return new exports.Boom(message, { statusCode: 415, data, ctor: exports.unsupportedMediaType });
304};
305
306
307exports.rangeNotSatisfiable = function (message, data) {
308
309 return new exports.Boom(message, { statusCode: 416, data, ctor: exports.rangeNotSatisfiable });
310};
311
312
313exports.expectationFailed = function (message, data) {
314
315 return new exports.Boom(message, { statusCode: 417, data, ctor: exports.expectationFailed });
316};
317
318
319exports.teapot = function (message, data) {
320
321 return new exports.Boom(message, { statusCode: 418, data, ctor: exports.teapot });
322};
323
324
325exports.badData = function (message, data) {
326
327 return new exports.Boom(message, { statusCode: 422, data, ctor: exports.badData });
328};
329
330
331exports.locked = function (message, data) {
332
333 return new exports.Boom(message, { statusCode: 423, data, ctor: exports.locked });
334};
335
336
337exports.failedDependency = function (message, data) {
338
339 return new exports.Boom(message, { statusCode: 424, data, ctor: exports.failedDependency });
340};
341
342exports.tooEarly = function (message, data) {
343
344 return new exports.Boom(message, { statusCode: 425, data, ctor: exports.tooEarly });
345};
346
347
348exports.preconditionRequired = function (message, data) {
349
350 return new exports.Boom(message, { statusCode: 428, data, ctor: exports.preconditionRequired });
351};
352
353
354exports.tooManyRequests = function (message, data) {
355
356 return new exports.Boom(message, { statusCode: 429, data, ctor: exports.tooManyRequests });
357};
358
359
360exports.illegal = function (message, data) {
361
362 return new exports.Boom(message, { statusCode: 451, data, ctor: exports.illegal });
363};
364
365
366// 5xx Server Errors
367
368exports.internal = function (message, data, statusCode = 500) {
369
370 return internals.serverError(message, data, statusCode, exports.internal);
371};
372
373
374exports.notImplemented = function (message, data) {
375
376 return internals.serverError(message, data, 501, exports.notImplemented);
377};
378
379
380exports.badGateway = function (message, data) {
381
382 return internals.serverError(message, data, 502, exports.badGateway);
383};
384
385
386exports.serverUnavailable = function (message, data) {
387
388 return internals.serverError(message, data, 503, exports.serverUnavailable);
389};
390
391
392exports.gatewayTimeout = function (message, data) {
393
394 return internals.serverError(message, data, 504, exports.gatewayTimeout);
395};
396
397
398exports.badImplementation = function (message, data) {
399
400 const err = internals.serverError(message, data, 500, exports.badImplementation);
401 err.isDeveloperError = true;
402 return err;
403};
404
405
406internals.initialize = function (err, statusCode, message) {
407
408 const numberCode = parseInt(statusCode, 10);
409 Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
410
411 err.isBoom = true;
412 err.isServer = numberCode >= 500;
413
414 if (!err.hasOwnProperty('data')) {
415 err.data = null;
416 }
417
418 err.output = {
419 statusCode: numberCode,
420 payload: {},
421 headers: {}
422 };
423
424 Object.defineProperty(err, 'reformat', { value: internals.reformat, configurable: true });
425
426 if (!message &&
427 !err.message) {
428
429 err.reformat();
430 message = err.output.payload.error;
431 }
432
433 if (message) {
434 const props = Object.getOwnPropertyDescriptor(err, 'message') || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(err), 'message');
435 Hoek.assert(!props || props.configurable && !props.get, 'The error is not compatible with boom');
436
437 err.message = message + (err.message ? ': ' + err.message : '');
438 err.output.payload.message = err.message;
439 }
440
441 err.reformat();
442 return err;
443};
444
445
446internals.reformat = function (debug = false) {
447
448 this.output.payload.statusCode = this.output.statusCode;
449 this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';
450
451 if (this.output.statusCode === 500 && debug !== true) {
452 this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
453 }
454 else if (this.message) {
455 this.output.payload.message = this.message;
456 }
457};
458
459
460internals.serverError = function (message, data, statusCode, ctor) {
461
462 if (data instanceof Error &&
463 !data.isBoom) {
464
465 return exports.boomify(data, { statusCode, message });
466 }
467
468 return new exports.Boom(message, { statusCode, data, ctor });
469};