UNPKG

12.7 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(messageOrError, options = {}) {
72
73 if (messageOrError instanceof Error) {
74 return exports.boomify(Hoek.clone(messageOrError), options);
75 }
76
77 const { statusCode = 500, data = null, ctor = exports.Boom } = options;
78 const error = new Error(messageOrError ? messageOrError : 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 (messageOrError, data) {
142
143 return new exports.Boom(messageOrError, { 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 const value = attributes[name] ?? '';
183
184 err.output.payload.attributes[name] = value;
185 return `${name}="${Hoek.escapeHeaderAttribute(value.toString())}"`;
186 })
187 .join(', ');
188 }
189 }
190
191 if (message) {
192 if (attributes) {
193 wwwAuthenticate += ',';
194 }
195
196 wwwAuthenticate += ` error="${Hoek.escapeHeaderAttribute(message)}"`;
197 err.output.payload.attributes.error = message;
198 }
199 else {
200 err.isMissing = true;
201 }
202
203 err.output.headers['WWW-Authenticate'] = wwwAuthenticate;
204 return err;
205};
206
207
208exports.paymentRequired = function (messageOrError, data) {
209
210 return new exports.Boom(messageOrError, { statusCode: 402, data, ctor: exports.paymentRequired });
211};
212
213
214exports.forbidden = function (messageOrError, data) {
215
216 return new exports.Boom(messageOrError, { statusCode: 403, data, ctor: exports.forbidden });
217};
218
219
220exports.notFound = function (messageOrError, data) {
221
222 return new exports.Boom(messageOrError, { statusCode: 404, data, ctor: exports.notFound });
223};
224
225
226exports.methodNotAllowed = function (messageOrError, data, allow) {
227
228 const err = new exports.Boom(messageOrError, { statusCode: 405, data, ctor: exports.methodNotAllowed });
229
230 if (typeof allow === 'string') {
231 allow = [allow];
232 }
233
234 if (Array.isArray(allow)) {
235 err.output.headers.Allow = allow.join(', ');
236 }
237
238 return err;
239};
240
241
242exports.notAcceptable = function (messageOrError, data) {
243
244 return new exports.Boom(messageOrError, { statusCode: 406, data, ctor: exports.notAcceptable });
245};
246
247
248exports.proxyAuthRequired = function (messageOrError, data) {
249
250 return new exports.Boom(messageOrError, { statusCode: 407, data, ctor: exports.proxyAuthRequired });
251};
252
253
254exports.clientTimeout = function (messageOrError, data) {
255
256 return new exports.Boom(messageOrError, { statusCode: 408, data, ctor: exports.clientTimeout });
257};
258
259
260exports.conflict = function (messageOrError, data) {
261
262 return new exports.Boom(messageOrError, { statusCode: 409, data, ctor: exports.conflict });
263};
264
265
266exports.resourceGone = function (messageOrError, data) {
267
268 return new exports.Boom(messageOrError, { statusCode: 410, data, ctor: exports.resourceGone });
269};
270
271
272exports.lengthRequired = function (messageOrError, data) {
273
274 return new exports.Boom(messageOrError, { statusCode: 411, data, ctor: exports.lengthRequired });
275};
276
277
278exports.preconditionFailed = function (messageOrError, data) {
279
280 return new exports.Boom(messageOrError, { statusCode: 412, data, ctor: exports.preconditionFailed });
281};
282
283
284exports.entityTooLarge = function (messageOrError, data) {
285
286 return new exports.Boom(messageOrError, { statusCode: 413, data, ctor: exports.entityTooLarge });
287};
288
289
290exports.uriTooLong = function (messageOrError, data) {
291
292 return new exports.Boom(messageOrError, { statusCode: 414, data, ctor: exports.uriTooLong });
293};
294
295
296exports.unsupportedMediaType = function (messageOrError, data) {
297
298 return new exports.Boom(messageOrError, { statusCode: 415, data, ctor: exports.unsupportedMediaType });
299};
300
301
302exports.rangeNotSatisfiable = function (messageOrError, data) {
303
304 return new exports.Boom(messageOrError, { statusCode: 416, data, ctor: exports.rangeNotSatisfiable });
305};
306
307
308exports.expectationFailed = function (messageOrError, data) {
309
310 return new exports.Boom(messageOrError, { statusCode: 417, data, ctor: exports.expectationFailed });
311};
312
313
314exports.teapot = function (messageOrError, data) {
315
316 return new exports.Boom(messageOrError, { statusCode: 418, data, ctor: exports.teapot });
317};
318
319
320exports.badData = function (messageOrError, data) {
321
322 return new exports.Boom(messageOrError, { statusCode: 422, data, ctor: exports.badData });
323};
324
325
326exports.locked = function (messageOrError, data) {
327
328 return new exports.Boom(messageOrError, { statusCode: 423, data, ctor: exports.locked });
329};
330
331
332exports.failedDependency = function (messageOrError, data) {
333
334 return new exports.Boom(messageOrError, { statusCode: 424, data, ctor: exports.failedDependency });
335};
336
337exports.tooEarly = function (messageOrError, data) {
338
339 return new exports.Boom(messageOrError, { statusCode: 425, data, ctor: exports.tooEarly });
340};
341
342
343exports.preconditionRequired = function (messageOrError, data) {
344
345 return new exports.Boom(messageOrError, { statusCode: 428, data, ctor: exports.preconditionRequired });
346};
347
348
349exports.tooManyRequests = function (messageOrError, data) {
350
351 return new exports.Boom(messageOrError, { statusCode: 429, data, ctor: exports.tooManyRequests });
352};
353
354
355exports.illegal = function (messageOrError, data) {
356
357 return new exports.Boom(messageOrError, { statusCode: 451, data, ctor: exports.illegal });
358};
359
360
361// 5xx Server Errors
362
363exports.internal = function (message, data, statusCode = 500) {
364
365 return internals.serverError(message, data, statusCode, exports.internal);
366};
367
368
369exports.notImplemented = function (message, data) {
370
371 return internals.serverError(message, data, 501, exports.notImplemented);
372};
373
374
375exports.badGateway = function (message, data) {
376
377 return internals.serverError(message, data, 502, exports.badGateway);
378};
379
380
381exports.serverUnavailable = function (message, data) {
382
383 return internals.serverError(message, data, 503, exports.serverUnavailable);
384};
385
386
387exports.gatewayTimeout = function (message, data) {
388
389 return internals.serverError(message, data, 504, exports.gatewayTimeout);
390};
391
392
393exports.badImplementation = function (message, data) {
394
395 const err = internals.serverError(message, data, 500, exports.badImplementation);
396 err.isDeveloperError = true;
397 return err;
398};
399
400
401internals.initialize = function (err, statusCode, message) {
402
403 const numberCode = parseInt(statusCode, 10);
404 Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
405
406 err.isBoom = true;
407 err.isServer = numberCode >= 500;
408
409 if (!err.hasOwnProperty('data')) {
410 err.data = null;
411 }
412
413 err.output = {
414 statusCode: numberCode,
415 payload: {},
416 headers: {}
417 };
418
419 Object.defineProperty(err, 'reformat', { value: internals.reformat, configurable: true });
420
421 if (!message &&
422 !err.message) {
423
424 err.reformat();
425 message = err.output.payload.error;
426 }
427
428 if (message) {
429 const props = Object.getOwnPropertyDescriptor(err, 'message') || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(err), 'message');
430 Hoek.assert(!props || props.configurable && !props.get, 'The error is not compatible with boom');
431
432 err.message = message + (err.message ? ': ' + err.message : '');
433 err.output.payload.message = err.message;
434 }
435
436 err.reformat();
437 return err;
438};
439
440
441internals.reformat = function (debug = false) {
442
443 this.output.payload.statusCode = this.output.statusCode;
444 this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';
445
446 if (this.output.statusCode === 500 && debug !== true) {
447 this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
448 }
449 else if (this.message) {
450 this.output.payload.message = this.message;
451 }
452};
453
454
455internals.serverError = function (messageOrError, data, statusCode, ctor) {
456
457 if (data instanceof Error &&
458 !data.isBoom) {
459
460 return exports.boomify(data, { statusCode, message: messageOrError });
461 }
462
463 return new exports.Boom(messageOrError, { statusCode, data, ctor });
464};