project files added
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!lib/**
|
||||
!.npmignore
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/wreck/issues?q=is%3Aissue+label%3A%22release+notes%22).
|
||||
|
||||
If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/wreck/milestones?state=closed&direction=asc&sort=due_date).
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2012-2018, Project contributors
|
||||
Copyright (c) 2012-2014, Walmart
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* The names of any contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+364
@@ -0,0 +1,364 @@
|
||||

|
||||
|
||||
### HTTP Client Utilities
|
||||
|
||||
[](https://npmjs.com/package/wreck)
|
||||
[](http://travis-ci.org/hapijs/wreck)
|
||||
|
||||
Lead Maintainer: [Wyatt Preul](https://github.com/geek)
|
||||
|
||||
## Usage
|
||||
|
||||
<!-- eslint-disable no-undef -->
|
||||
<!-- eslint-disable no-unused-vars -->
|
||||
```javascript
|
||||
const Wreck = require('wreck');
|
||||
|
||||
const example = async function () {
|
||||
|
||||
const { res, payload } = await Wreck.get('http://example.com');
|
||||
console.log(payload.toString());
|
||||
};
|
||||
|
||||
try {
|
||||
example();
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced
|
||||
<!-- eslint-disable no-undef -->
|
||||
<!-- eslint-disable no-unused-vars -->
|
||||
```javascript
|
||||
const Wreck = require('wreck');
|
||||
|
||||
const method = 'GET'; // GET, POST, PUT, DELETE
|
||||
const uri = '/';
|
||||
const readableStream = Wreck.toReadableStream('foo=bar');
|
||||
|
||||
const wreck = Wreck.defaults({
|
||||
headers: { 'x-foo-bar': 123 },
|
||||
agents: {
|
||||
https: new Https.Agent({ maxSockets: 100 }),
|
||||
http: new Http.Agent({ maxSockets: 1000 }),
|
||||
httpsAllowUnauthorized: new Https.Agent({ maxSockets: 100, rejectUnauthorized: false })
|
||||
}
|
||||
});
|
||||
|
||||
// cascading example -- does not alter `wreck`
|
||||
// inherits `headers` and `agents` specified above
|
||||
const wreckWithTimeout = wreck.defaults({
|
||||
timeout: 5
|
||||
});
|
||||
|
||||
// all attributes are optional
|
||||
const options = {
|
||||
baseUrl: 'https://www.example.com',
|
||||
payload: readableStream || 'foo=bar' || Buffer.from('foo=bar'),
|
||||
headers: { /* http headers */ },
|
||||
redirects: 3,
|
||||
beforeRedirect: (redirectMethod, statusCode, location, resHeaders, redirectOptions, next) => next(),
|
||||
redirected: function (statusCode, location, req) {},
|
||||
timeout: 1000, // 1 second, default: unlimited
|
||||
maxBytes: 1048576, // 1 MB, default: unlimited
|
||||
rejectUnauthorized: true || false,
|
||||
downstreamRes: null,
|
||||
agent: null, // Node Core http.Agent
|
||||
secureProtocol: 'SSLv3_method', // The SSL method to use
|
||||
ciphers: 'DES-CBC3-SHA' // The TLS ciphers to support
|
||||
};
|
||||
|
||||
const example = async function () {
|
||||
|
||||
const promise = wreck.request(method, uri, options);
|
||||
try {
|
||||
const res = await promise;
|
||||
const body = await Wreck.read(res, options);
|
||||
console.log(body.toString());
|
||||
}
|
||||
catch (err) {
|
||||
// Handle errors
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Use `promise.req.abort()` to terminate the request early. Note that this is limited to the initial request only.
|
||||
If the request was already redirected, aborting the original request will not abort execution of pending redirections.
|
||||
|
||||
|
||||
### `defaults(options)`
|
||||
|
||||
Returns a *new* instance of Wreck which merges the provided `options` with those provided on a per-request basis. You can call defaults repeatedly to build up multiple http clients.
|
||||
- `options` - Config object containing settings for both `request` and `read` operations.
|
||||
|
||||
### `request(method, uri, [options])`
|
||||
|
||||
Initiate an HTTP request.
|
||||
- `method` - A string specifying the HTTP request method, defaulting to 'GET'.
|
||||
- `uri` - The URI of the requested resource.
|
||||
- `options` - An optional configuration object. To omit this argument but still
|
||||
use a callback, pass `null` in this position. The options object supports the
|
||||
following optional keys:
|
||||
- `baseUrl` - fully qualified uri string used as the base url. Most useful with `request.defaults`, for example when you want to do many requests to the same domain.
|
||||
If `baseUrl` is `https://example.com/api/`, then requesting `/end/point?test=true` will fetch `https://example.com/api/end/point?test=true`. Any
|
||||
querystring in the `baseUrl` will be overwritten with the querystring in the `uri` When `baseUrl` is given, `uri` must also be a string.
|
||||
- `socketPath` - `/path/to/unix/socket` for Server.
|
||||
- `payload` - The request body as a string, Buffer, Readable Stream, or an object that can be serialized using `JSON.stringify()`.
|
||||
- `headers` - An object containing request headers.
|
||||
- `redirects` - The maximum number of redirects to follow.
|
||||
- `redirect303` - if `true`, a HTTP 303 status code will redirect using a GET method. Defaults to no redirection on 303.
|
||||
- `beforeRedirect` - A callback function that is called before a redirect is triggered, using the signature
|
||||
`function(redirectMethod, statusCode, location, resHeaders, redirectOptions, next)` where:
|
||||
- `redirectMethod` - A string specifying the redirect method.
|
||||
- `statusCode` - HTTP status code of the response that triggered the redirect.
|
||||
- `location` - The redirect location string.
|
||||
- `resHeaders` - An object with the headers received as part of the redirection response.
|
||||
- `redirectOptions` - Options that will be applied to the redirect request. Changes to this object are applied to the redirection request.
|
||||
- `next` - the callback function called to perform the redirection using signature `function()`.
|
||||
- `redirected` - A callback function that is called when a redirect was triggered, using the signature `function(statusCode, location, req)` where:
|
||||
- `statusCode` - HTTP status code of the response that triggered the redirect.
|
||||
- `location` - The redirected location string.
|
||||
- `req` - The new [ClientRequest](http://nodejs.org/api/http.html#http_class_http_clientrequest) object which replaces the one initially returned.
|
||||
- `timeout` - The number of milliseconds to wait without receiving a response
|
||||
before aborting the request. Defaults to unlimited.
|
||||
- `rejectUnauthorized` - [TLS](http://nodejs.org/api/tls.html) flag indicating
|
||||
whether the client should reject a response from a server with invalid certificates. This cannot be set at the
|
||||
same time as the `agent` option is set.
|
||||
- `downstreamRes`: downstream Resource dependency.
|
||||
- `agent` - Node Core [http.Agent](http://nodejs.org/api/http.html#http_class_http_agent).
|
||||
Defaults to either `wreck.agents.http` or `wreck.agents.https`. Setting to `false` disables agent pooling.
|
||||
- `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method`
|
||||
to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs
|
||||
for possible [SSL_METHODS](http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS).
|
||||
- `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default.
|
||||
The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs
|
||||
for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT).
|
||||
|
||||
Returns a promise that resolves into a node response object. The promise has a `req` property which is the instance of the node.js
|
||||
[ClientRequest](http://nodejs.org/api/http.html#http_class_http_clientrequest) object.
|
||||
|
||||
### `read(response, options)`
|
||||
- `response` - An HTTP Incoming Message object.
|
||||
- `options` - `null` or a configuration object with the following optional keys:
|
||||
- `timeout` - The number of milliseconds to wait while reading data before
|
||||
aborting handling of the response. Defaults to unlimited.
|
||||
- `json` - A value indicating how to try to parse the payload as JSON. Defaults to `undefined` meaning no parse logic.
|
||||
- `true`, 'smart' - only try `JSON.parse` if the response indicates a JSON content-type.
|
||||
- `strict` - as 'smart', except returns an error for non-JSON content-type.
|
||||
- `force` - try `JSON.parse` regardless of the content-type header.
|
||||
- `gunzip` - A value indicating the behavior to adopt when the payload is gzipped. Defaults to `undefined` meaning no gunzipping.
|
||||
- `true` - only try to gunzip if the response indicates a gzip content-encoding.
|
||||
- `false` - explicitly disable gunzipping.
|
||||
- `force` - try to gunzip regardless of the content-encoding header.
|
||||
- `maxBytes` - The maximum allowed response payload size. Defaults to unlimited.
|
||||
|
||||
Returns a promise that resolves into the payload in the form of a Buffer or (optionally) parsed JavaScript object (JSON).
|
||||
|
||||
#### Notes about gunzip
|
||||
|
||||
When using gunzip, HTTP headers `Content-Encoding`, `Content-Length`, `Content-Range` and `ETag` won't reflect the reality as the payload has been uncompressed.
|
||||
|
||||
### `get(uri, [options])`
|
||||
|
||||
Convenience method for GET operations.
|
||||
- `uri` - The URI of the requested resource.
|
||||
- `options` - Optional config object containing settings for both `request` and
|
||||
`read` operations.
|
||||
|
||||
Returns a promise that resolves into an object with the following properties:
|
||||
- `res` - The [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
||||
object, which is a readable stream that has "ended" and contains no more data to read.
|
||||
- `payload` - The payload in the form of a Buffer or (optionally) parsed JavaScript object (JSON).
|
||||
|
||||
Throws any error that may have occurred during handling of the request or a Boom error object if the response has an error status
|
||||
code (i.e. 4xx or 5xx). If the error is a boom error object it will have the following properties in addition to the standard boom
|
||||
properties:
|
||||
- `data.isResponseError` - boolean, indicates if the error is a result of an error response status code
|
||||
- `data.headers` - object containing the response headers
|
||||
- `data.payload` - the payload in the form of a Buffer or as a parsed object
|
||||
- `data.res` - the [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) object
|
||||
|
||||
### `post(uri, [options])`
|
||||
|
||||
Convenience method for POST operations.
|
||||
- `uri` - The URI of the requested resource.
|
||||
- `options` - Optional config object containing settings for both `request` and
|
||||
`read` operations.
|
||||
|
||||
Returns a promise that resolves into an object with the following properties:
|
||||
- `res` - The [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
||||
object, which is a readable stream that has "ended" and contains no more data to read.
|
||||
- `payload` - The payload in the form of a Buffer or (optionally) parsed JavaScript object (JSON).
|
||||
|
||||
Throws any error that may have occurred during handling of the request or a Boom error object if the response has an error status
|
||||
code (i.e. 4xx or 5xx). If the error is a boom error object it will have the following properties in addition to the standard boom
|
||||
properties:
|
||||
- `data.isResponseError` - boolean, indicates if the error is a result of an error response status code
|
||||
- `data.headers` - object containing the response headers
|
||||
- `data.payload` - the payload in the form of a Buffer or as a parsed object
|
||||
- `data.res` - the [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) object
|
||||
|
||||
### `patch(uri, [options])`
|
||||
|
||||
Convenience method for PATCH operations.
|
||||
- `uri` - The URI of the requested resource.
|
||||
- `options` - Optional config object containing settings for both `request` and
|
||||
`read` operations.
|
||||
|
||||
Returns a promise that resolves into an object with the following properties:
|
||||
- `res` - The [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
||||
object, which is a readable stream that has "ended" and contains no more data to read.
|
||||
- `payload` - The payload in the form of a Buffer or (optionally) parsed JavaScript object (JSON).
|
||||
|
||||
Throws any error that may have occurred during handling of the request or a Boom error object if the response has an error status
|
||||
code (i.e. 4xx or 5xx). If the error is a boom error object it will have the following properties in addition to the standard boom
|
||||
properties:
|
||||
- `data.isResponseError` - boolean, indicates if the error is a result of an error response status code
|
||||
- `data.headers` - object containing the response headers
|
||||
- `data.payload` - the payload in the form of a Buffer or as a parsed object
|
||||
- `data.res` - the [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) object
|
||||
|
||||
### `put(uri, [options])`
|
||||
|
||||
Convenience method for PUT operations.
|
||||
- `uri` - The URI of the requested resource.
|
||||
- `options` - Optional config object containing settings for both `request` and
|
||||
`read` operations.
|
||||
|
||||
Returns a promise that resolves into an object with the following properties:
|
||||
- `res` - The [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
||||
object, which is a readable stream that has "ended" and contains no more data to read.
|
||||
- `payload` - The payload in the form of a Buffer or (optionally) parsed JavaScript object (JSON).
|
||||
|
||||
Throws any error that may have occurred during handling of the request or a Boom error object if the response has an error status
|
||||
code (i.e. 4xx or 5xx). If the error is a boom error object it will have the following properties in addition to the standard boom
|
||||
properties:
|
||||
- `data.isResponseError` - boolean, indicates if the error is a result of an error response status code
|
||||
- `data.headers` - object containing the response headers
|
||||
- `data.payload` - the payload in the form of a Buffer or as a parsed object
|
||||
- `data.res` - the [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) object
|
||||
|
||||
### `delete(uri, [options])`
|
||||
|
||||
Convenience method for DELETE operations.
|
||||
- `uri` - The URI of the requested resource.
|
||||
- `options` - Optional config object containing settings for both `request` and
|
||||
`read` operations.
|
||||
|
||||
Returns a promise that resolves into an object with the following properties:
|
||||
- `res` - The [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
||||
object, which is a readable stream that has "ended" and contains no more data to read.
|
||||
- `payload` - The payload in the form of a Buffer or (optionally) parsed JavaScript object (JSON).
|
||||
|
||||
Throws any error that may have occurred during handling of the request or a Boom error object if the response has an error status
|
||||
code (i.e. 4xx or 5xx). If the error is a boom error object it will have the following properties in addition to the standard boom
|
||||
properties:
|
||||
- `data.isResponseError` - boolean, indicates if the error is a result of an error response status code
|
||||
- `data.headers` - object containing the response headers
|
||||
- `data.payload` - the payload in the form of a Buffer or as a parsed object
|
||||
- `data.res` - the [HTTP Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) object
|
||||
|
||||
### `toReadableStream(payload, [encoding])`
|
||||
|
||||
Creates a [readable stream](http://nodejs.org/api/stream.html#stream_class_stream_readable)
|
||||
for the provided payload and encoding.
|
||||
- `payload` - The Buffer or string to be wrapped in a readable stream.
|
||||
- `encoding` - The encoding to use. Must be a valid Buffer encoding, such as 'utf8' or 'ascii'.
|
||||
|
||||
<!-- eslint-disable no-unused-vars -->
|
||||
<!-- eslint-disable no-undef -->
|
||||
```javascript
|
||||
const stream = Wreck.toReadableStream(Buffer.from('Hello', 'ascii'), 'ascii');
|
||||
const read = stream.read();
|
||||
// read -> 'Hello'
|
||||
```
|
||||
|
||||
### `parseCacheControl(field)`
|
||||
|
||||
Parses the provided *cache-control* request header value into an object containing
|
||||
a property for each directive and it's value. Boolean directives, such as "private"
|
||||
or "no-cache" will be set to the boolean `true`.
|
||||
- `field` - The header cache control value to be parsed.
|
||||
|
||||
<!-- eslint-disable no-unused-vars -->
|
||||
<!-- eslint-disable no-undef -->
|
||||
```javascript
|
||||
const result = Wreck.parseCacheControl('private, max-age=0, no-cache');
|
||||
// result.private -> true
|
||||
// result['max-age'] -> 0
|
||||
// result['no-cache'] -> true
|
||||
```
|
||||
|
||||
### `agents`
|
||||
|
||||
Object that contains the agents for pooling connections for `http` and `https`.
|
||||
The properties are `http`, `https`, and `httpsAllowUnauthorized` which is an
|
||||
`https` agent with `rejectUnauthorized` set to false. All agents have
|
||||
`maxSockets` configured to `Infinity`. They are each instances of the Node.js
|
||||
[Agent](http://nodejs.org/api/http.html#http_class_http_agent) and expose the
|
||||
standard properties.
|
||||
|
||||
For example, the following code demonstrates changing `maxSockets` on the `http`
|
||||
agent.
|
||||
|
||||
```js
|
||||
const Wreck = require('wreck');
|
||||
|
||||
Wreck.agents.http.maxSockets = 20;
|
||||
```
|
||||
|
||||
Below is another example that sets the certificate details for all HTTPS requests.
|
||||
|
||||
<!-- eslint-disable no-undef -->
|
||||
```js
|
||||
const HTTPS = require('https');
|
||||
const Wreck = require('wreck');
|
||||
|
||||
Wreck.agents.https = new HTTPS.Agent({
|
||||
cert,
|
||||
key,
|
||||
ca
|
||||
});
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
To enable events, use `Wreck.defaults({ events: true })`. Events are available via the
|
||||
`events` emitter attached to the client returned by `Wreck.defaults()`.
|
||||
|
||||
#### `request`
|
||||
|
||||
The request event is emitted just before *wreck* makes a request. The
|
||||
handler should accept the following arguments `(uri, options)` where:
|
||||
|
||||
- `uri` - the result of `Url.parse(uri)`. This will provide information about
|
||||
the resource requested. Also includes the headers and method.
|
||||
- `options` - the options passed into the request function. This will include
|
||||
a payload if there is one.
|
||||
|
||||
Since the `request` event executes on a global event handler, you can intercept
|
||||
and decorate a request before its sent.
|
||||
|
||||
|
||||
#### `response`
|
||||
|
||||
The response event is always emitted for any request that *wreck* makes. The
|
||||
handler should accept the following arguments `(err, details)` where:
|
||||
|
||||
- `err` - a Boom error
|
||||
- `details` - object with the following properties
|
||||
- `req` - the raw `ClientHttp` request object
|
||||
- `res` - the raw `IncomingMessage` response object
|
||||
- `start` - the time that the request was initiated
|
||||
- `uri` - the result of `Url.parse(uri)`. This will provide information about
|
||||
the resource requested. Also includes the headers and method.
|
||||
|
||||
This event is useful for logging all requests that go through *wreck*. The `err`
|
||||
and `res` arguments can be undefined depending on if an error occurs. Please
|
||||
be aware that if multiple modules are depending on the same cached *wreck*
|
||||
module that this event can fire for each request made across all modules. The
|
||||
`start` property is the timestamp when the request was started. This can be
|
||||
useful for determining how long it takes *wreck* to get a response back and
|
||||
processed.
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('events');
|
||||
const Http = require('http');
|
||||
const Https = require('https');
|
||||
const Stream = require('stream');
|
||||
const Url = require('url');
|
||||
const Zlib = require('zlib');
|
||||
|
||||
const Boom = require('boom');
|
||||
const Bourne = require('bourne');
|
||||
const Hoek = require('hoek');
|
||||
|
||||
const Payload = require('./payload');
|
||||
const Recorder = require('./recorder');
|
||||
const Tap = require('./tap');
|
||||
|
||||
|
||||
const internals = {
|
||||
jsonRegex: /^application\/([a-z0-9.]*[+-]json|json)$/,
|
||||
shallowOptions: ['agent', 'agents', 'beforeRedirect', 'downstreamRes', 'payload', 'redirected']
|
||||
};
|
||||
|
||||
|
||||
// New instance is exported as module.exports
|
||||
|
||||
internals.Client = function (options = {}) {
|
||||
|
||||
Hoek.assert(!options.agents || (options.agents.https && options.agents.http && options.agents.httpsAllowUnauthorized), 'Option agents must include "http", "https", and "httpsAllowUnauthorized"');
|
||||
|
||||
this._defaults = Hoek.cloneWithShallow(options, internals.shallowOptions);
|
||||
|
||||
this.agents = this._defaults.agents || {
|
||||
https: new Https.Agent({ maxSockets: Infinity }),
|
||||
http: new Http.Agent({ maxSockets: Infinity }),
|
||||
httpsAllowUnauthorized: new Https.Agent({ maxSockets: Infinity, rejectUnauthorized: false })
|
||||
};
|
||||
|
||||
if (!options.events) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.events = new Events.EventEmitter();
|
||||
this._emit = function (...args) {
|
||||
|
||||
this.events.emit(...args);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.defaults = function (options) {
|
||||
|
||||
Hoek.assert(options && (typeof options === 'object'), 'options must be provided to defaults');
|
||||
|
||||
options = Hoek.applyToDefaultsWithShallow(this._defaults, options, internals.shallowOptions);
|
||||
return new internals.Client(options);
|
||||
};
|
||||
|
||||
|
||||
internals.resolveUrl = function (baseUrl, path) {
|
||||
|
||||
if (!path) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
const parsedPath = Url.parse(path);
|
||||
if (parsedPath.host) {
|
||||
Hoek.assert(parsedPath.protocol, 'Invalid destination path missing protocol');
|
||||
return Url.format(parsedPath);
|
||||
}
|
||||
|
||||
const parsedBase = Url.parse(baseUrl);
|
||||
parsedBase.pathname = parsedBase.pathname + parsedPath.pathname;
|
||||
parsedBase.pathname = parsedBase.pathname.replace(/[/]{2,}/g, '/');
|
||||
parsedBase.search = parsedPath.search; // Always use the querystring from the path argument
|
||||
|
||||
return Url.format(parsedBase);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.request = function (method, url, options = {}) {
|
||||
|
||||
try {
|
||||
options = Hoek.applyToDefaultsWithShallow(this._defaults, options, internals.shallowOptions);
|
||||
|
||||
Hoek.assert(options.payload === undefined || typeof options.payload === 'string' || typeof options.payload === 'object', 'options.payload must be a string, a Buffer, a Stream, or an Object');
|
||||
Hoek.assert((options.agent === undefined || options.agent === null) || (typeof options.rejectUnauthorized !== 'boolean'), 'options.agent cannot be set to an Agent at the same time as options.rejectUnauthorized is set');
|
||||
Hoek.assert(options.beforeRedirect === undefined || options.beforeRedirect === null || typeof options.beforeRedirect === 'function', 'options.beforeRedirect must be a function');
|
||||
Hoek.assert(options.redirected === undefined || options.redirected === null || typeof options.redirected === 'function', 'options.redirected must be a function');
|
||||
Hoek.assert(options.gunzip === undefined || typeof options.gunzip === 'boolean' || options.gunzip === 'force', 'options.gunzip must be a boolean or "force"');
|
||||
}
|
||||
catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
options.beforeRedirect = options.beforeRedirect || ((redirectMethod, statusCode, location, resHeaders, redirectOptions, next) => next());
|
||||
|
||||
if (options.baseUrl) {
|
||||
url = internals.resolveUrl(options.baseUrl, url);
|
||||
delete options.baseUrl;
|
||||
}
|
||||
|
||||
const relay = {};
|
||||
const req = this._request(method, url, options, relay);
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
|
||||
relay.callback = (err, res) => {
|
||||
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(res);
|
||||
return;
|
||||
};
|
||||
});
|
||||
|
||||
promise.req = req;
|
||||
return promise;
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype._request = function (method, url, options, relay, _trace) {
|
||||
|
||||
const uri = {};
|
||||
let parsedUri;
|
||||
if (options.socketPath) {
|
||||
uri.socketPath = options.socketPath;
|
||||
delete options.socketPath;
|
||||
parsedUri = Url.parse(url);
|
||||
}
|
||||
else {
|
||||
uri.setHost = false;
|
||||
parsedUri = new Url.URL(url);
|
||||
}
|
||||
|
||||
internals.applyUrlToOptions(uri, parsedUri);
|
||||
|
||||
uri.method = method.toUpperCase();
|
||||
uri.headers = options.headers || {};
|
||||
uri.headers.host = parsedUri.host;
|
||||
const hasContentLength = internals.findHeader('content-length', uri.headers) !== undefined;
|
||||
|
||||
if (options.payload && typeof options.payload === 'object' && !(options.payload instanceof Stream) && !Buffer.isBuffer(options.payload)) {
|
||||
options.payload = JSON.stringify(options.payload);
|
||||
if (!internals.findHeader('content-type', uri.headers)) {
|
||||
uri.headers['content-type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
if (options.gunzip &&
|
||||
internals.findHeader('accept-encoding', uri.headers) === undefined) {
|
||||
|
||||
uri.headers['accept-encoding'] = 'gzip';
|
||||
}
|
||||
|
||||
const payloadSupported = (uri.method !== 'GET' && uri.method !== 'HEAD' && options.payload !== null && options.payload !== undefined);
|
||||
if (payloadSupported &&
|
||||
(typeof options.payload === 'string' || Buffer.isBuffer(options.payload)) &&
|
||||
(!hasContentLength)) {
|
||||
|
||||
uri.headers = Hoek.clone(uri.headers);
|
||||
uri.headers['content-length'] = Buffer.isBuffer(options.payload) ? options.payload.length : Buffer.byteLength(options.payload);
|
||||
}
|
||||
|
||||
let redirects = (options.hasOwnProperty('redirects') ? options.redirects : false); // Needed to allow 0 as valid value when passed recursively
|
||||
|
||||
_trace = (_trace || []);
|
||||
_trace.push({ method: uri.method, url });
|
||||
|
||||
const client = (uri.protocol === 'https:' ? Https : Http);
|
||||
|
||||
if (options.rejectUnauthorized !== undefined && uri.protocol === 'https:') {
|
||||
uri.agent = options.rejectUnauthorized ? this.agents.https : this.agents.httpsAllowUnauthorized;
|
||||
}
|
||||
else if (options.agent || options.agent === false) {
|
||||
uri.agent = options.agent;
|
||||
}
|
||||
else {
|
||||
uri.agent = uri.protocol === 'https:' ? this.agents.https : this.agents.http;
|
||||
}
|
||||
|
||||
if (options.secureProtocol !== undefined) {
|
||||
uri.secureProtocol = options.secureProtocol;
|
||||
}
|
||||
|
||||
if (options.ciphers !== undefined) {
|
||||
uri.ciphers = options.ciphers;
|
||||
}
|
||||
|
||||
if (this._emit) {
|
||||
this._emit('request', uri, options);
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
const req = client.request(uri);
|
||||
|
||||
let shadow = null; // A copy of the streamed request payload when redirects are enabled
|
||||
let timeoutId;
|
||||
|
||||
const onError = (err) => {
|
||||
|
||||
err.trace = _trace;
|
||||
return finishOnce(Boom.badGateway('Client request error', err));
|
||||
};
|
||||
|
||||
req.once('error', onError);
|
||||
|
||||
const onResponse = (res) => {
|
||||
|
||||
// Pass-through response
|
||||
|
||||
const statusCode = res.statusCode;
|
||||
const redirectMethod = internals.redirectMethod(statusCode, uri.method, options);
|
||||
|
||||
if (redirects === false ||
|
||||
!redirectMethod) {
|
||||
|
||||
return finishOnce(null, res);
|
||||
}
|
||||
|
||||
// Redirection
|
||||
|
||||
res.destroy();
|
||||
|
||||
if (redirects === 0) {
|
||||
return finishOnce(Boom.badGateway('Maximum redirections reached', _trace));
|
||||
}
|
||||
|
||||
let location = res.headers.location;
|
||||
if (!location) {
|
||||
return finishOnce(Boom.badGateway('Received redirection without location', _trace));
|
||||
}
|
||||
|
||||
if (!/^https?:/i.test(location)) {
|
||||
location = Url.resolve(uri.href, location);
|
||||
}
|
||||
|
||||
const redirectOptions = Hoek.cloneWithShallow(options, internals.shallowOptions);
|
||||
redirectOptions.payload = shadow || options.payload; // shadow must be ready at this point if set
|
||||
redirectOptions.redirects = --redirects;
|
||||
|
||||
return options.beforeRedirect(redirectMethod, statusCode, location, res.headers, redirectOptions, () => {
|
||||
|
||||
const redirectReq = this._request(redirectMethod, location, redirectOptions, { callback: finishOnce }, _trace);
|
||||
|
||||
if (options.redirected) {
|
||||
options.redirected(statusCode, location, redirectReq);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Register handlers
|
||||
|
||||
const finish = (err, res) => {
|
||||
|
||||
if (err) {
|
||||
req.abort();
|
||||
}
|
||||
|
||||
req.removeListener('response', onResponse);
|
||||
req.removeListener('error', onError);
|
||||
req.on('error', Hoek.ignore);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (this._emit) {
|
||||
this._emit('response', err, { req, res, start, uri });
|
||||
}
|
||||
|
||||
return relay.callback(err, res);
|
||||
};
|
||||
|
||||
const finishOnce = Hoek.once(finish);
|
||||
|
||||
req.once('response', onResponse);
|
||||
|
||||
if (options.timeout) {
|
||||
timeoutId = setTimeout(() => {
|
||||
|
||||
return finishOnce(Boom.gatewayTimeout('Client request timeout'));
|
||||
}, options.timeout);
|
||||
delete options.timeout;
|
||||
}
|
||||
|
||||
// Custom abort method to detect early aborts
|
||||
|
||||
const _abort = req.abort;
|
||||
let aborted = false;
|
||||
req.abort = () => {
|
||||
|
||||
if (!aborted && !req.res && !req.socket) {
|
||||
process.nextTick(() => {
|
||||
|
||||
// Fake an ECONNRESET error
|
||||
|
||||
const error = new Error('socket hang up');
|
||||
error.code = 'ECONNRESET';
|
||||
finishOnce(error);
|
||||
});
|
||||
}
|
||||
|
||||
aborted = true;
|
||||
return _abort.call(req);
|
||||
};
|
||||
|
||||
// Write payload
|
||||
|
||||
if (payloadSupported) {
|
||||
if (options.payload instanceof Stream) {
|
||||
let stream = options.payload;
|
||||
|
||||
if (redirects) {
|
||||
const collector = new Tap();
|
||||
collector.once('finish', () => {
|
||||
|
||||
shadow = collector.collect();
|
||||
});
|
||||
|
||||
stream = options.payload.pipe(collector);
|
||||
}
|
||||
|
||||
internals.deferPipeUntilSocketConnects(req, stream);
|
||||
return req;
|
||||
}
|
||||
|
||||
req.write(options.payload);
|
||||
}
|
||||
|
||||
// Finalize request
|
||||
|
||||
req.end();
|
||||
return req;
|
||||
};
|
||||
|
||||
|
||||
internals.deferPipeUntilSocketConnects = function (req, stream) {
|
||||
|
||||
const onSocket = (socket) => {
|
||||
|
||||
if (!socket.connecting) {
|
||||
return onSocketConnect();
|
||||
}
|
||||
|
||||
socket.once('connect', onSocketConnect);
|
||||
};
|
||||
|
||||
const onSocketConnect = () => {
|
||||
|
||||
stream.pipe(req);
|
||||
stream.removeListener('error', onStreamError);
|
||||
};
|
||||
|
||||
const onStreamError = (err) => {
|
||||
|
||||
req.emit('error', err);
|
||||
};
|
||||
|
||||
req.once('socket', onSocket);
|
||||
stream.on('error', onStreamError);
|
||||
};
|
||||
|
||||
|
||||
internals.redirectMethod = function (code, method, options) {
|
||||
|
||||
switch (code) {
|
||||
case 301:
|
||||
case 302:
|
||||
return method;
|
||||
|
||||
case 303:
|
||||
if (options.redirect303) {
|
||||
return 'GET';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 307:
|
||||
case 308:
|
||||
return method;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.read = function (res, options = {}) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this._read(res, options, (err, payload) => {
|
||||
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(payload);
|
||||
return;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype._read = function (res, options, callback) {
|
||||
|
||||
options = Hoek.applyToDefaultsWithShallow(this._defaults, options, internals.shallowOptions);
|
||||
|
||||
// Finish once
|
||||
|
||||
let clientTimeoutId = null;
|
||||
|
||||
const finish = (err, buffer) => {
|
||||
|
||||
clearTimeout(clientTimeoutId);
|
||||
reader.removeListener('error', onReaderError);
|
||||
reader.removeListener('finish', onReaderFinish);
|
||||
res.removeListener('error', onResError);
|
||||
res.removeListener('close', onResAborted);
|
||||
res.removeListener('aborted', onResAborted);
|
||||
res.on('error', Hoek.ignore);
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!options.json) {
|
||||
return callback(null, buffer);
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
|
||||
if (buffer.length === 0) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
if (options.json === 'force') {
|
||||
return internals.tryParseBuffer(buffer, callback);
|
||||
}
|
||||
|
||||
// mode is "smart", "strict" or true
|
||||
|
||||
const contentType = (res.headers && internals.findHeader('content-type', res.headers)) || '';
|
||||
const mime = contentType.split(';')[0].trim().toLowerCase();
|
||||
|
||||
if (!internals.jsonRegex.test(mime)) {
|
||||
if (options.json === 'strict') {
|
||||
return callback(Boom.notAcceptable('The content-type is not JSON compatible'));
|
||||
}
|
||||
|
||||
return callback(null, buffer);
|
||||
}
|
||||
|
||||
return internals.tryParseBuffer(buffer, callback);
|
||||
};
|
||||
|
||||
const finishOnce = Hoek.once(finish);
|
||||
|
||||
const clientTimeout = options.timeout;
|
||||
if (clientTimeout &&
|
||||
clientTimeout > 0) {
|
||||
|
||||
clientTimeoutId = setTimeout(() => {
|
||||
|
||||
finishOnce(Boom.clientTimeout());
|
||||
}, clientTimeout);
|
||||
}
|
||||
|
||||
// Hander errors
|
||||
|
||||
const onResError = (err) => {
|
||||
|
||||
return finishOnce(err.isBoom ? err : Boom.internal('Payload stream error', err));
|
||||
};
|
||||
|
||||
const onResAborted = () => {
|
||||
|
||||
// Workaround https://github.com/nodejs/node/pull/20611
|
||||
// This is covered in node 10
|
||||
/* $lab:coverage:off$ */
|
||||
if (res.complete) {
|
||||
return;
|
||||
}
|
||||
/* $lab:coverage:on$ */
|
||||
|
||||
return finishOnce(Boom.internal('Payload stream closed prematurely'));
|
||||
};
|
||||
|
||||
res.once('error', onResError);
|
||||
res.once('close', onResAborted);
|
||||
res.once('aborted', onResAborted);
|
||||
|
||||
// Read payload
|
||||
|
||||
const reader = new Recorder({ maxBytes: options.maxBytes });
|
||||
|
||||
const onReaderError = (err) => {
|
||||
|
||||
if (res.destroy) { // GZip stream has no destroy() method
|
||||
res.destroy();
|
||||
}
|
||||
|
||||
return finishOnce(err);
|
||||
};
|
||||
|
||||
reader.once('error', onReaderError);
|
||||
|
||||
const onReaderFinish = () => {
|
||||
|
||||
return finishOnce(null, reader.collect());
|
||||
};
|
||||
|
||||
reader.once('finish', onReaderFinish);
|
||||
|
||||
if (options.gunzip) {
|
||||
const contentEncoding = options.gunzip === 'force' ?
|
||||
'gzip' :
|
||||
(res.headers && internals.findHeader('content-encoding', res.headers)) || '';
|
||||
|
||||
if (/^(x-)?gzip(\s*,\s*identity)?$/.test(contentEncoding)) {
|
||||
const gunzip = Zlib.createGunzip();
|
||||
|
||||
gunzip.once('error', onReaderError);
|
||||
|
||||
res.pipe(gunzip).pipe(reader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.pipe(reader);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.toReadableStream = function (payload, encoding) {
|
||||
|
||||
return new Payload(payload, encoding);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.parseCacheControl = function (field) {
|
||||
|
||||
/*
|
||||
Cache-Control = 1#cache-directive
|
||||
cache-directive = token [ "=" ( token / quoted-string ) ]
|
||||
token = [^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+
|
||||
quoted-string = "(?:[^"\\]|\\.)*"
|
||||
*/
|
||||
|
||||
// 1: directive = 2: token 3: quoted-string
|
||||
const regex = /(?:^|(?:\s*\,\s*))([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g;
|
||||
|
||||
const header = {};
|
||||
const error = field.replace(regex, ($0, $1, $2, $3) => {
|
||||
|
||||
const value = $2 || $3;
|
||||
header[$1] = value ? value.toLowerCase() : true;
|
||||
return '';
|
||||
});
|
||||
|
||||
if (header['max-age']) {
|
||||
try {
|
||||
const maxAge = parseInt(header['max-age'], 10);
|
||||
if (isNaN(maxAge)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
header['max-age'] = maxAge;
|
||||
}
|
||||
catch (err) { }
|
||||
}
|
||||
|
||||
return (error ? null : header);
|
||||
};
|
||||
|
||||
|
||||
// Shortcuts
|
||||
|
||||
internals.Client.prototype.get = function (uri, options) {
|
||||
|
||||
return this._shortcut('GET', uri, options);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.post = function (uri, options) {
|
||||
|
||||
return this._shortcut('POST', uri, options);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.patch = function (uri, options) {
|
||||
|
||||
return this._shortcut('PATCH', uri, options);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.put = function (uri, options) {
|
||||
|
||||
return this._shortcut('PUT', uri, options);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype.delete = function (uri, options) {
|
||||
|
||||
return this._shortcut('DELETE', uri, options);
|
||||
};
|
||||
|
||||
|
||||
internals.Client.prototype._shortcut = async function (method, uri, options = {}) {
|
||||
|
||||
const res = await this.request(method, uri, options);
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = await this.read(res, options);
|
||||
}
|
||||
catch (err) {
|
||||
err.data = err.data || {};
|
||||
err.data.res = res;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (res.statusCode < 400) {
|
||||
return { res, payload };
|
||||
}
|
||||
|
||||
// Response error
|
||||
|
||||
const data = {
|
||||
isResponseError: true,
|
||||
headers: res.headers,
|
||||
res,
|
||||
payload
|
||||
};
|
||||
|
||||
throw new Boom(`Response Error: ${res.statusCode} ${res.statusMessage}`, { statusCode: res.statusCode, data });
|
||||
};
|
||||
|
||||
|
||||
internals.tryParseBuffer = function (buffer, next) {
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = Bourne.parse(buffer.toString());
|
||||
}
|
||||
catch (err) {
|
||||
return next(Boom.badGateway(err.message, { payload: buffer }));
|
||||
}
|
||||
|
||||
return next(null, payload);
|
||||
};
|
||||
|
||||
|
||||
internals.findHeader = function (headerName, headers) {
|
||||
|
||||
const foundKey = Object.keys(headers)
|
||||
.find((key) => key.toLowerCase() === headerName.toLowerCase());
|
||||
|
||||
return foundKey && headers[foundKey];
|
||||
};
|
||||
|
||||
internals.applyUrlToOptions = (options, url) => {
|
||||
|
||||
options.origin = url.origin;
|
||||
options.searchParams = url.searchParams;
|
||||
options.protocol = url.protocol;
|
||||
options.hostname = url.hostname;
|
||||
options.hash = url.hash;
|
||||
options.search = url.search;
|
||||
options.pathname = url.pathname;
|
||||
options.path = `${url.pathname}${url.search || ''}`;
|
||||
options.href = url.href;
|
||||
if (url.port !== '') {
|
||||
options.port = Number(url.port);
|
||||
}
|
||||
|
||||
if (url.username || url.password) {
|
||||
options.auth = `${url.username}:${url.password}`;
|
||||
options.username = url.username;
|
||||
options.password = url.password;
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
|
||||
module.exports = new internals.Client();
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const Stream = require('stream');
|
||||
|
||||
|
||||
const internals = {};
|
||||
|
||||
|
||||
module.exports = internals.Payload = class extends Stream.Readable {
|
||||
|
||||
constructor(payload, encoding) {
|
||||
|
||||
super();
|
||||
|
||||
const data = [].concat(payload || '');
|
||||
let size = 0;
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
const chunk = data[i];
|
||||
size = size + chunk.length;
|
||||
data[i] = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
}
|
||||
|
||||
this._data = Buffer.concat(data, size);
|
||||
this._position = 0;
|
||||
this._encoding = encoding || 'utf8';
|
||||
}
|
||||
|
||||
_read(size) {
|
||||
|
||||
const chunk = this._data.slice(this._position, this._position + size);
|
||||
this.push(chunk, this._encoding);
|
||||
this._position = this._position + chunk.length;
|
||||
|
||||
if (this._position >= this._data.length) {
|
||||
this.push(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const Stream = require('stream');
|
||||
|
||||
const Boom = require('boom');
|
||||
|
||||
|
||||
const internals = {};
|
||||
|
||||
|
||||
module.exports = internals.Recorder = class extends Stream.Writable {
|
||||
|
||||
constructor(options) {
|
||||
|
||||
super();
|
||||
|
||||
this.settings = options; // No need to clone since called internally with new object
|
||||
this.buffers = [];
|
||||
this.length = 0;
|
||||
}
|
||||
|
||||
_write(chunk, encoding, next) {
|
||||
|
||||
if (this.settings.maxBytes &&
|
||||
this.length + chunk.length > this.settings.maxBytes) {
|
||||
|
||||
return this.emit('error', Boom.entityTooLarge('Payload content length greater than maximum allowed: ' + this.settings.maxBytes));
|
||||
}
|
||||
|
||||
this.length = this.length + chunk.length;
|
||||
this.buffers.push(chunk);
|
||||
next();
|
||||
}
|
||||
|
||||
collect() {
|
||||
|
||||
const buffer = (this.buffers.length === 0 ? Buffer.alloc(0) : (this.buffers.length === 1 ? this.buffers[0] : Buffer.concat(this.buffers, this.length)));
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const Stream = require('stream');
|
||||
const Payload = require('./payload');
|
||||
|
||||
|
||||
const internals = {};
|
||||
|
||||
|
||||
module.exports = internals.Tap = class extends Stream.Transform {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
this.buffers = [];
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, next) {
|
||||
|
||||
this.buffers.push(chunk);
|
||||
next(null, chunk);
|
||||
}
|
||||
|
||||
collect() {
|
||||
|
||||
return new Payload(this.buffers);
|
||||
}
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/boom/issues?q=is%3Aissue+label%3A%22release+notes%22).
|
||||
|
||||
If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/boom/milestones?state=closed&direction=asc&sort=due_date).
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2012-2018, Project contributors.
|
||||
Copyright (c) 2012-2014, Walmart.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* The names of any contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+810
@@ -0,0 +1,810 @@
|
||||

|
||||
|
||||
HTTP-friendly error objects
|
||||
|
||||
[](http://travis-ci.org/hapijs/boom)
|
||||
[](https://www.npmjs.com/package/boom)
|
||||
|
||||
Lead Maintainer: [Eran Hammer](https://github.com/hueniverse)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Boom](#boom)
|
||||
- [`reformat(debug)`](#reformatdebug)
|
||||
- [Helper Methods](#helper-methods)
|
||||
- [`new Boom(message, [options])`](#new-boommessage-options)
|
||||
- [`boomify(err, [options])`](#boomifyerr-options)
|
||||
- [`isBoom(err)`](#isboomerr)
|
||||
- [HTTP 4xx Errors](#http-4xx-errors)
|
||||
- [`Boom.badRequest([message], [data])`](#boombadrequestmessage-data)
|
||||
- [`Boom.unauthorized([message], [scheme], [attributes])`](#boomunauthorizedmessage-scheme-attributes)
|
||||
- [`Boom.paymentRequired([message], [data])`](#boompaymentrequiredmessage-data)
|
||||
- [`Boom.forbidden([message], [data])`](#boomforbiddenmessage-data)
|
||||
- [`Boom.notFound([message], [data])`](#boomnotfoundmessage-data)
|
||||
- [`Boom.methodNotAllowed([message], [data], [allow])`](#boommethodnotallowedmessage-data-allow)
|
||||
- [`Boom.notAcceptable([message], [data])`](#boomnotacceptablemessage-data)
|
||||
- [`Boom.proxyAuthRequired([message], [data])`](#boomproxyauthrequiredmessage-data)
|
||||
- [`Boom.clientTimeout([message], [data])`](#boomclienttimeoutmessage-data)
|
||||
- [`Boom.conflict([message], [data])`](#boomconflictmessage-data)
|
||||
- [`Boom.resourceGone([message], [data])`](#boomresourcegonemessage-data)
|
||||
- [`Boom.lengthRequired([message], [data])`](#boomlengthrequiredmessage-data)
|
||||
- [`Boom.preconditionFailed([message], [data])`](#boompreconditionfailedmessage-data)
|
||||
- [`Boom.entityTooLarge([message], [data])`](#boomentitytoolargemessage-data)
|
||||
- [`Boom.uriTooLong([message], [data])`](#boomuritoolongmessage-data)
|
||||
- [`Boom.unsupportedMediaType([message], [data])`](#boomunsupportedmediatypemessage-data)
|
||||
- [`Boom.rangeNotSatisfiable([message], [data])`](#boomrangenotsatisfiablemessage-data)
|
||||
- [`Boom.expectationFailed([message], [data])`](#boomexpectationfailedmessage-data)
|
||||
- [`Boom.teapot([message], [data])`](#boomteapotmessage-data)
|
||||
- [`Boom.badData([message], [data])`](#boombaddatamessage-data)
|
||||
- [`Boom.locked([message], [data])`](#boomlockedmessage-data)
|
||||
- [`Boom.failedDependency([message], [data])`](#boomfaileddependencymessage-data)
|
||||
- [`Boom.preconditionRequired([message], [data])`](#boompreconditionrequiredmessage-data)
|
||||
- [`Boom.tooManyRequests([message], [data])`](#boomtoomanyrequestsmessage-data)
|
||||
- [`Boom.illegal([message], [data])`](#boomillegalmessage-data)
|
||||
- [HTTP 5xx Errors](#http-5xx-errors)
|
||||
- [`Boom.badImplementation([message], [data])` - (*alias: `internal`*)](#boombadimplementationmessage-data---alias-internal)
|
||||
- [`Boom.notImplemented([message], [data])`](#boomnotimplementedmessage-data)
|
||||
- [`Boom.badGateway([message], [data])`](#boombadgatewaymessage-data)
|
||||
- [`Boom.serverUnavailable([message], [data])`](#boomserverunavailablemessage-data)
|
||||
- [`Boom.gatewayTimeout([message], [data])`](#boomgatewaytimeoutmessage-data)
|
||||
- [F.A.Q.](#faq)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# Boom
|
||||
|
||||
**boom** provides a set of utilities for returning HTTP errors. Each utility returns a `Boom`
|
||||
error response object which includes the following properties:
|
||||
- `isBoom` - if `true`, indicates this is a `Boom` object instance. Note that this boolean should
|
||||
only be used if the error is an instance of `Error`. If it is not certain, use `Boom.isBoom()`
|
||||
instead.
|
||||
- `isServer` - convenience bool indicating status code >= 500.
|
||||
- `message` - the error message.
|
||||
- `typeof` - the constructor used to create the error (e.g. `Boom.badRequest`).
|
||||
- `output` - the formatted response. Can be directly manipulated after object construction to return a custom
|
||||
error response. Allowed root keys:
|
||||
- `statusCode` - the HTTP status code (typically 4xx or 5xx).
|
||||
- `headers` - an object containing any HTTP headers where each key is a header name and value is the header content.
|
||||
- `payload` - the formatted object used as the response payload (stringified). Can be directly manipulated but any
|
||||
changes will be lost
|
||||
if `reformat()` is called. Any content allowed and by default includes the following content:
|
||||
- `statusCode` - the HTTP status code, derived from `error.output.statusCode`.
|
||||
- `error` - the HTTP status message (e.g. 'Bad Request', 'Internal Server Error') derived from `statusCode`.
|
||||
- `message` - the error message derived from `error.message`.
|
||||
- inherited `Error` properties.
|
||||
|
||||
The `Boom` object also supports the following method:
|
||||
|
||||
### `reformat(debug)`
|
||||
|
||||
Rebuilds `error.output` using the other object properties where:
|
||||
|
||||
- `debug` - a Boolean that, when `true`, causes Internal Server Error messages to be left in tact. Defaults to `false`, meaning that Internal Server Error messages are redacted.
|
||||
|
||||
Note that `Boom` object will return `true` when used with `instanceof Boom`, but do not use the
|
||||
`Boom` prototype (they are either plain `Error` or the error prototype passed in). This means
|
||||
`Boom` objects should only be tested using `instanceof Boom` or `Boom.isBoom()` but not by looking
|
||||
at the prototype or contructor information. This limitation is to avoid manipulating the prototype
|
||||
chain which is very slow.
|
||||
|
||||
## Helper Methods
|
||||
|
||||
### `new Boom(message, [options])`
|
||||
|
||||
Creates a new `Boom` object using the provided `message` and then calling
|
||||
[`boomify()`](#boomifyerr-options) to decorate the error with the `Boom` properties, where:
|
||||
- `message` - the error message. If `message` is an error, it is the same as calling
|
||||
[`boomify()`](#boomifyerr-options) directly.
|
||||
- `options` - and optional object where:
|
||||
- `statusCode` - the HTTP status code. Defaults to `500` if no status code is already set.
|
||||
- `data` - additional error information (assigned to `error.data`).
|
||||
- `decorate` - an option with extra properties to set on the error object.
|
||||
- `ctor` - constructor reference used to crop the exception call stack output.
|
||||
- if `message` is an error object, also supports the other [`boomify()`](#boomifyerr-options)
|
||||
options.
|
||||
|
||||
### `boomify(err, [options])`
|
||||
|
||||
Decorates an error with the `Boom` properties where:
|
||||
- `err` - the `Error` object to decorate.
|
||||
- `options` - optional object with the following optional settings:
|
||||
- `statusCode` - the HTTP status code. Defaults to `500` if no status code is already set and `err` is not a `Boom` object.
|
||||
- `message` - error message string. If the error already has a message, the provided `message` is added as a prefix.
|
||||
Defaults to no message.
|
||||
- `decorate` - an option with extra properties to set on the error object.
|
||||
- `override` - if `false`, the `err` provided is a `Boom` object, and a `statusCode` or `message` are provided,
|
||||
the values are ignored. Defaults to `true` (apply the provided `statusCode` and `message` options to the error
|
||||
regardless of its type, `Error` or `Boom` object).
|
||||
|
||||
```js
|
||||
var error = new Error('Unexpected input');
|
||||
Boom.boomify(error, { statusCode: 400 });
|
||||
```
|
||||
|
||||
### `isBoom(err)`
|
||||
|
||||
Identifies whether an error is a `Boom` object. Same as calling `instanceof Boom`.
|
||||
|
||||
## HTTP 4xx Errors
|
||||
|
||||
### `Boom.badRequest([message], [data])`
|
||||
|
||||
Returns a 400 Bad Request error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.badRequest('invalid query');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 400,
|
||||
"error": "Bad Request",
|
||||
"message": "invalid query"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.unauthorized([message], [scheme], [attributes])`
|
||||
|
||||
Returns a 401 Unauthorized error where:
|
||||
- `message` - optional message.
|
||||
- `scheme` can be one of the following:
|
||||
- an authentication scheme name
|
||||
- an array of string values. These values will be separated by ', ' and set to the 'WWW-Authenticate' header.
|
||||
- `attributes` - an object of values to use while setting the 'WWW-Authenticate' header. This value is only used
|
||||
when `scheme` is a string, otherwise it is ignored. Every key/value pair will be included in the
|
||||
'WWW-Authenticate' in the format of 'key="value"' as well as in the response payload under the `attributes` key. Alternatively value can be a string which is use to set the value of the scheme, for example setting the token value for negotiate header. If string is used message parameter must be null.
|
||||
`null` and `undefined` will be replaced with an empty string. If `attributes` is set, `message` will be used as
|
||||
the 'error' segment of the 'WWW-Authenticate' header. If `message` is unset, the 'error' segment of the header
|
||||
will not be present and `isMissing` will be true on the error object.
|
||||
|
||||
If either `scheme` or `attributes` are set, the resultant `Boom` object will have the
|
||||
'WWW-Authenticate' header set for the response.
|
||||
|
||||
```js
|
||||
Boom.unauthorized('invalid password');
|
||||
```
|
||||
|
||||
Generates the following response:
|
||||
|
||||
```json
|
||||
"payload": {
|
||||
"statusCode": 401,
|
||||
"error": "Unauthorized",
|
||||
"message": "invalid password"
|
||||
},
|
||||
"headers" {}
|
||||
```
|
||||
|
||||
```js
|
||||
Boom.unauthorized('invalid password', 'sample');
|
||||
```
|
||||
|
||||
Generates the following response:
|
||||
|
||||
```json
|
||||
"payload": {
|
||||
"statusCode": 401,
|
||||
"error": "Unauthorized",
|
||||
"message": "invalid password",
|
||||
"attributes": {
|
||||
"error": "invalid password"
|
||||
}
|
||||
},
|
||||
"headers" {
|
||||
"WWW-Authenticate": "sample error=\"invalid password\""
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
Boom.unauthorized(null, 'Negotiate', 'VGhpcyBpcyBhIHRlc3QgdG9rZW4=');
|
||||
```
|
||||
|
||||
Generates the following response:
|
||||
|
||||
```json
|
||||
"payload": {
|
||||
"statusCode": 401,
|
||||
"error": "Unauthorized",
|
||||
"attributes": "VGhpcyBpcyBhIHRlc3QgdG9rZW4="
|
||||
},
|
||||
"headers" {
|
||||
"WWW-Authenticate": "Negotiate VGhpcyBpcyBhIHRlc3QgdG9rZW4="
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
Boom.unauthorized('invalid password', 'sample', { ttl: 0, cache: null, foo: 'bar' });
|
||||
```
|
||||
|
||||
Generates the following response:
|
||||
|
||||
```json
|
||||
"payload": {
|
||||
"statusCode": 401,
|
||||
"error": "Unauthorized",
|
||||
"message": "invalid password",
|
||||
"attributes": {
|
||||
"error": "invalid password",
|
||||
"ttl": 0,
|
||||
"cache": "",
|
||||
"foo": "bar"
|
||||
}
|
||||
},
|
||||
"headers" {
|
||||
"WWW-Authenticate": "sample ttl=\"0\", cache=\"\", foo=\"bar\", error=\"invalid password\""
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.paymentRequired([message], [data])`
|
||||
|
||||
Returns a 402 Payment Required error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.paymentRequired('bandwidth used');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 402,
|
||||
"error": "Payment Required",
|
||||
"message": "bandwidth used"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.forbidden([message], [data])`
|
||||
|
||||
Returns a 403 Forbidden error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.forbidden('try again some time');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 403,
|
||||
"error": "Forbidden",
|
||||
"message": "try again some time"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.notFound([message], [data])`
|
||||
|
||||
Returns a 404 Not Found error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.notFound('missing');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 404,
|
||||
"error": "Not Found",
|
||||
"message": "missing"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.methodNotAllowed([message], [data], [allow])`
|
||||
|
||||
Returns a 405 Method Not Allowed error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
- `allow` - optional string or array of strings (to be combined and separated by ', ') which is set to the 'Allow' header.
|
||||
|
||||
```js
|
||||
Boom.methodNotAllowed('that method is not allowed');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 405,
|
||||
"error": "Method Not Allowed",
|
||||
"message": "that method is not allowed"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.notAcceptable([message], [data])`
|
||||
|
||||
Returns a 406 Not Acceptable error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.notAcceptable('unacceptable');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 406,
|
||||
"error": "Not Acceptable",
|
||||
"message": "unacceptable"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.proxyAuthRequired([message], [data])`
|
||||
|
||||
Returns a 407 Proxy Authentication Required error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.proxyAuthRequired('auth missing');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 407,
|
||||
"error": "Proxy Authentication Required",
|
||||
"message": "auth missing"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.clientTimeout([message], [data])`
|
||||
|
||||
Returns a 408 Request Time-out error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.clientTimeout('timed out');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 408,
|
||||
"error": "Request Time-out",
|
||||
"message": "timed out"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.conflict([message], [data])`
|
||||
|
||||
Returns a 409 Conflict error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.conflict('there was a conflict');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 409,
|
||||
"error": "Conflict",
|
||||
"message": "there was a conflict"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.resourceGone([message], [data])`
|
||||
|
||||
Returns a 410 Gone error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.resourceGone('it is gone');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 410,
|
||||
"error": "Gone",
|
||||
"message": "it is gone"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.lengthRequired([message], [data])`
|
||||
|
||||
Returns a 411 Length Required error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.lengthRequired('length needed');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 411,
|
||||
"error": "Length Required",
|
||||
"message": "length needed"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.preconditionFailed([message], [data])`
|
||||
|
||||
Returns a 412 Precondition Failed error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.preconditionFailed();
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 412,
|
||||
"error": "Precondition Failed"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.entityTooLarge([message], [data])`
|
||||
|
||||
Returns a 413 Request Entity Too Large error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.entityTooLarge('too big');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 413,
|
||||
"error": "Request Entity Too Large",
|
||||
"message": "too big"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.uriTooLong([message], [data])`
|
||||
|
||||
Returns a 414 Request-URI Too Large error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.uriTooLong('uri is too long');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 414,
|
||||
"error": "Request-URI Too Large",
|
||||
"message": "uri is too long"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.unsupportedMediaType([message], [data])`
|
||||
|
||||
Returns a 415 Unsupported Media Type error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.unsupportedMediaType('that media is not supported');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 415,
|
||||
"error": "Unsupported Media Type",
|
||||
"message": "that media is not supported"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.rangeNotSatisfiable([message], [data])`
|
||||
|
||||
Returns a 416 Requested Range Not Satisfiable error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.rangeNotSatisfiable();
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 416,
|
||||
"error": "Requested Range Not Satisfiable"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.expectationFailed([message], [data])`
|
||||
|
||||
Returns a 417 Expectation Failed error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.expectationFailed('expected this to work');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 417,
|
||||
"error": "Expectation Failed",
|
||||
"message": "expected this to work"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.teapot([message], [data])`
|
||||
|
||||
Returns a 418 I'm a Teapot error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.teapot('sorry, no coffee...');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 418,
|
||||
"error": "I'm a Teapot",
|
||||
"message": "Sorry, no coffee..."
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.badData([message], [data])`
|
||||
|
||||
Returns a 422 Unprocessable Entity error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.badData('your data is bad and you should feel bad');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 422,
|
||||
"error": "Unprocessable Entity",
|
||||
"message": "your data is bad and you should feel bad"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.locked([message], [data])`
|
||||
|
||||
Returns a 423 Locked error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.locked('this resource has been locked');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 423,
|
||||
"error": "Locked",
|
||||
"message": "this resource has been locked"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.failedDependency([message], [data])`
|
||||
|
||||
Returns a 424 Failed Dependency error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.failedDependency('an external resource failed');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 424,
|
||||
"error": "Failed Dependency",
|
||||
"message": "an external resource failed"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.preconditionRequired([message], [data])`
|
||||
|
||||
Returns a 428 Precondition Required error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.preconditionRequired('you must supply an If-Match header');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 428,
|
||||
"error": "Precondition Required",
|
||||
"message": "you must supply an If-Match header"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.tooManyRequests([message], [data])`
|
||||
|
||||
Returns a 429 Too Many Requests error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.tooManyRequests('you have exceeded your request limit');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 429,
|
||||
"error": "Too Many Requests",
|
||||
"message": "you have exceeded your request limit"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.illegal([message], [data])`
|
||||
|
||||
Returns a 451 Unavailable For Legal Reasons error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.illegal('you are not permitted to view this resource for legal reasons');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 451,
|
||||
"error": "Unavailable For Legal Reasons",
|
||||
"message": "you are not permitted to view this resource for legal reasons"
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP 5xx Errors
|
||||
|
||||
All 500 errors hide your message from the end user. Your message is recorded in the server log.
|
||||
|
||||
### `Boom.badImplementation([message], [data])` - (*alias: `internal`*)
|
||||
|
||||
Returns a 500 Internal Server Error error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.badImplementation('terrible implementation');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 500,
|
||||
"error": "Internal Server Error",
|
||||
"message": "An internal server error occurred"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.notImplemented([message], [data])`
|
||||
|
||||
Returns a 501 Not Implemented error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.notImplemented('method not implemented');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 501,
|
||||
"error": "Not Implemented",
|
||||
"message": "method not implemented"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.badGateway([message], [data])`
|
||||
|
||||
Returns a 502 Bad Gateway error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.badGateway('that is a bad gateway');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 502,
|
||||
"error": "Bad Gateway",
|
||||
"message": "that is a bad gateway"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.serverUnavailable([message], [data])`
|
||||
|
||||
Returns a 503 Service Unavailable error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.serverUnavailable('unavailable');
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 503,
|
||||
"error": "Service Unavailable",
|
||||
"message": "unavailable"
|
||||
}
|
||||
```
|
||||
|
||||
### `Boom.gatewayTimeout([message], [data])`
|
||||
|
||||
Returns a 504 Gateway Time-out error where:
|
||||
- `message` - optional message.
|
||||
- `data` - optional additional error data.
|
||||
|
||||
```js
|
||||
Boom.gatewayTimeout();
|
||||
```
|
||||
|
||||
Generates the following response payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 504,
|
||||
"error": "Gateway Time-out"
|
||||
}
|
||||
```
|
||||
|
||||
## F.A.Q.
|
||||
|
||||
**Q** How do I include extra information in my responses? `output.payload` is missing `data`, what gives?
|
||||
|
||||
**A** There is a reason the values passed back in the response payloads are pretty locked down. It's mostly for security and to not leak any important information back to the client. This means you will need to put in a little more effort to include extra information about your custom error. Check out the ["Error transformation"](https://github.com/hapijs/hapi/blob/master/API.md#error-transformation) section in the hapi documentation.
|
||||
|
||||
---
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
'use strict';
|
||||
|
||||
// Load modules
|
||||
|
||||
const Hoek = require('hoek');
|
||||
|
||||
|
||||
// Declare internals
|
||||
|
||||
const internals = {
|
||||
codes: new Map([
|
||||
[100, 'Continue'],
|
||||
[101, 'Switching Protocols'],
|
||||
[102, 'Processing'],
|
||||
[200, 'OK'],
|
||||
[201, 'Created'],
|
||||
[202, 'Accepted'],
|
||||
[203, 'Non-Authoritative Information'],
|
||||
[204, 'No Content'],
|
||||
[205, 'Reset Content'],
|
||||
[206, 'Partial Content'],
|
||||
[207, 'Multi-Status'],
|
||||
[300, 'Multiple Choices'],
|
||||
[301, 'Moved Permanently'],
|
||||
[302, 'Moved Temporarily'],
|
||||
[303, 'See Other'],
|
||||
[304, 'Not Modified'],
|
||||
[305, 'Use Proxy'],
|
||||
[307, 'Temporary Redirect'],
|
||||
[400, 'Bad Request'],
|
||||
[401, 'Unauthorized'],
|
||||
[402, 'Payment Required'],
|
||||
[403, 'Forbidden'],
|
||||
[404, 'Not Found'],
|
||||
[405, 'Method Not Allowed'],
|
||||
[406, 'Not Acceptable'],
|
||||
[407, 'Proxy Authentication Required'],
|
||||
[408, 'Request Time-out'],
|
||||
[409, 'Conflict'],
|
||||
[410, 'Gone'],
|
||||
[411, 'Length Required'],
|
||||
[412, 'Precondition Failed'],
|
||||
[413, 'Request Entity Too Large'],
|
||||
[414, 'Request-URI Too Large'],
|
||||
[415, 'Unsupported Media Type'],
|
||||
[416, 'Requested Range Not Satisfiable'],
|
||||
[417, 'Expectation Failed'],
|
||||
[418, 'I\'m a teapot'],
|
||||
[422, 'Unprocessable Entity'],
|
||||
[423, 'Locked'],
|
||||
[424, 'Failed Dependency'],
|
||||
[425, 'Unordered Collection'],
|
||||
[426, 'Upgrade Required'],
|
||||
[428, 'Precondition Required'],
|
||||
[429, 'Too Many Requests'],
|
||||
[431, 'Request Header Fields Too Large'],
|
||||
[451, 'Unavailable For Legal Reasons'],
|
||||
[500, 'Internal Server Error'],
|
||||
[501, 'Not Implemented'],
|
||||
[502, 'Bad Gateway'],
|
||||
[503, 'Service Unavailable'],
|
||||
[504, 'Gateway Time-out'],
|
||||
[505, 'HTTP Version Not Supported'],
|
||||
[506, 'Variant Also Negotiates'],
|
||||
[507, 'Insufficient Storage'],
|
||||
[509, 'Bandwidth Limit Exceeded'],
|
||||
[510, 'Not Extended'],
|
||||
[511, 'Network Authentication Required']
|
||||
])
|
||||
};
|
||||
|
||||
|
||||
module.exports = internals.Boom = class extends Error {
|
||||
|
||||
static [Symbol.hasInstance](instance) {
|
||||
|
||||
return internals.Boom.isBoom(instance);
|
||||
}
|
||||
|
||||
constructor(message, options = {}) {
|
||||
|
||||
if (message instanceof Error) {
|
||||
return internals.Boom.boomify(Hoek.clone(message), options);
|
||||
}
|
||||
|
||||
const { statusCode = 500, data = null, ctor = internals.Boom } = options;
|
||||
const error = new Error(message ? message : undefined); // Avoids settings null message
|
||||
Error.captureStackTrace(error, ctor); // Filter the stack to our external API
|
||||
error.data = data;
|
||||
internals.initialize(error, statusCode);
|
||||
error.typeof = ctor;
|
||||
|
||||
if (options.decorate) {
|
||||
Object.assign(error, options.decorate);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static isBoom(err) {
|
||||
|
||||
return (err instanceof Error && !!err.isBoom);
|
||||
}
|
||||
|
||||
static boomify(err, options) {
|
||||
|
||||
Hoek.assert(err instanceof Error, 'Cannot wrap non-Error object');
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.data !== undefined) {
|
||||
err.data = options.data;
|
||||
}
|
||||
|
||||
if (options.decorate) {
|
||||
Object.assign(err, options.decorate);
|
||||
}
|
||||
|
||||
if (!err.isBoom) {
|
||||
return internals.initialize(err, options.statusCode || 500, options.message);
|
||||
}
|
||||
|
||||
if (options.override === false || // Defaults to true
|
||||
(!options.statusCode && !options.message)) {
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return internals.initialize(err, options.statusCode || err.output.statusCode, options.message);
|
||||
}
|
||||
|
||||
// 4xx Client Errors
|
||||
|
||||
static badRequest(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 400, data, ctor: internals.Boom.badRequest });
|
||||
}
|
||||
|
||||
static unauthorized(message, scheme, attributes) { // Or function (message, wwwAuthenticate[])
|
||||
|
||||
const err = new internals.Boom(message, { statusCode: 401, ctor: internals.Boom.unauthorized });
|
||||
|
||||
if (!scheme) {
|
||||
return err;
|
||||
}
|
||||
|
||||
let wwwAuthenticate = '';
|
||||
|
||||
if (typeof scheme === 'string') {
|
||||
|
||||
// function (message, scheme, attributes)
|
||||
|
||||
wwwAuthenticate = scheme;
|
||||
|
||||
if (attributes || message) {
|
||||
err.output.payload.attributes = {};
|
||||
}
|
||||
|
||||
if (attributes) {
|
||||
if (typeof attributes === 'string') {
|
||||
wwwAuthenticate = wwwAuthenticate + ' ' + Hoek.escapeHeaderAttribute(attributes);
|
||||
err.output.payload.attributes = attributes;
|
||||
}
|
||||
else {
|
||||
const names = Object.keys(attributes);
|
||||
for (let i = 0; i < names.length; ++i) {
|
||||
const name = names[i];
|
||||
if (i) {
|
||||
wwwAuthenticate = wwwAuthenticate + ',';
|
||||
}
|
||||
|
||||
let value = attributes[name];
|
||||
if (value === null ||
|
||||
value === undefined) { // Value can be zero
|
||||
|
||||
value = '';
|
||||
}
|
||||
|
||||
wwwAuthenticate = wwwAuthenticate + ' ' + name + '="' + Hoek.escapeHeaderAttribute(value.toString()) + '"';
|
||||
err.output.payload.attributes[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (message) {
|
||||
if (attributes) {
|
||||
wwwAuthenticate = wwwAuthenticate + ',';
|
||||
}
|
||||
|
||||
wwwAuthenticate = wwwAuthenticate + ' error="' + Hoek.escapeHeaderAttribute(message) + '"';
|
||||
err.output.payload.attributes.error = message;
|
||||
}
|
||||
else {
|
||||
err.isMissing = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// function (message, wwwAuthenticate[])
|
||||
|
||||
const wwwArray = scheme;
|
||||
for (let i = 0; i < wwwArray.length; ++i) {
|
||||
if (i) {
|
||||
wwwAuthenticate = wwwAuthenticate + ', ';
|
||||
}
|
||||
|
||||
wwwAuthenticate = wwwAuthenticate + wwwArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
err.output.headers['WWW-Authenticate'] = wwwAuthenticate;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static paymentRequired(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 402, data, ctor: internals.Boom.paymentRequired });
|
||||
}
|
||||
|
||||
static forbidden(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 403, data, ctor: internals.Boom.forbidden });
|
||||
}
|
||||
|
||||
static notFound(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 404, data, ctor: internals.Boom.notFound });
|
||||
}
|
||||
|
||||
static methodNotAllowed(message, data, allow) {
|
||||
|
||||
const err = new internals.Boom(message, { statusCode: 405, data, ctor: internals.Boom.methodNotAllowed });
|
||||
|
||||
if (typeof allow === 'string') {
|
||||
allow = [allow];
|
||||
}
|
||||
|
||||
if (Array.isArray(allow)) {
|
||||
err.output.headers.Allow = allow.join(', ');
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static notAcceptable(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 406, data, ctor: internals.Boom.notAcceptable });
|
||||
}
|
||||
|
||||
static proxyAuthRequired(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 407, data, ctor: internals.Boom.proxyAuthRequired });
|
||||
}
|
||||
|
||||
static clientTimeout(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 408, data, ctor: internals.Boom.clientTimeout });
|
||||
}
|
||||
|
||||
static conflict(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 409, data, ctor: internals.Boom.conflict });
|
||||
}
|
||||
|
||||
static resourceGone(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 410, data, ctor: internals.Boom.resourceGone });
|
||||
}
|
||||
|
||||
static lengthRequired(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 411, data, ctor: internals.Boom.lengthRequired });
|
||||
}
|
||||
|
||||
static preconditionFailed(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 412, data, ctor: internals.Boom.preconditionFailed });
|
||||
}
|
||||
|
||||
static entityTooLarge(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 413, data, ctor: internals.Boom.entityTooLarge });
|
||||
}
|
||||
|
||||
static uriTooLong(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 414, data, ctor: internals.Boom.uriTooLong });
|
||||
}
|
||||
|
||||
static unsupportedMediaType(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 415, data, ctor: internals.Boom.unsupportedMediaType });
|
||||
}
|
||||
|
||||
static rangeNotSatisfiable(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 416, data, ctor: internals.Boom.rangeNotSatisfiable });
|
||||
}
|
||||
|
||||
static expectationFailed(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 417, data, ctor: internals.Boom.expectationFailed });
|
||||
}
|
||||
|
||||
static teapot(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 418, data, ctor: internals.Boom.teapot });
|
||||
}
|
||||
|
||||
static badData(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 422, data, ctor: internals.Boom.badData });
|
||||
}
|
||||
|
||||
static locked(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 423, data, ctor: internals.Boom.locked });
|
||||
}
|
||||
|
||||
static failedDependency(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 424, data, ctor: internals.Boom.failedDependency });
|
||||
}
|
||||
|
||||
static preconditionRequired(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 428, data, ctor: internals.Boom.preconditionRequired });
|
||||
}
|
||||
|
||||
static tooManyRequests(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 429, data, ctor: internals.Boom.tooManyRequests });
|
||||
}
|
||||
|
||||
static illegal(message, data) {
|
||||
|
||||
return new internals.Boom(message, { statusCode: 451, data, ctor: internals.Boom.illegal });
|
||||
}
|
||||
|
||||
// 5xx Server Errors
|
||||
|
||||
static internal(message, data, statusCode = 500) {
|
||||
|
||||
return internals.serverError(message, data, statusCode, internals.Boom.internal);
|
||||
}
|
||||
|
||||
static notImplemented(message, data) {
|
||||
|
||||
return internals.serverError(message, data, 501, internals.Boom.notImplemented);
|
||||
}
|
||||
|
||||
static badGateway(message, data) {
|
||||
|
||||
return internals.serverError(message, data, 502, internals.Boom.badGateway);
|
||||
}
|
||||
|
||||
static serverUnavailable(message, data) {
|
||||
|
||||
return internals.serverError(message, data, 503, internals.Boom.serverUnavailable);
|
||||
}
|
||||
|
||||
static gatewayTimeout(message, data) {
|
||||
|
||||
return internals.serverError(message, data, 504, internals.Boom.gatewayTimeout);
|
||||
}
|
||||
|
||||
static badImplementation(message, data) {
|
||||
|
||||
const err = internals.serverError(message, data, 500, internals.Boom.badImplementation);
|
||||
err.isDeveloperError = true;
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
internals.initialize = function (err, statusCode, message) {
|
||||
|
||||
const numberCode = parseInt(statusCode, 10);
|
||||
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
|
||||
|
||||
err.isBoom = true;
|
||||
err.isServer = numberCode >= 500;
|
||||
|
||||
if (!err.hasOwnProperty('data')) {
|
||||
err.data = null;
|
||||
}
|
||||
|
||||
err.output = {
|
||||
statusCode: numberCode,
|
||||
payload: {},
|
||||
headers: {}
|
||||
};
|
||||
|
||||
err.reformat = internals.reformat;
|
||||
|
||||
if (!message &&
|
||||
!err.message) {
|
||||
|
||||
err.reformat();
|
||||
message = err.output.payload.error;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
err.message = (message + (err.message ? ': ' + err.message : ''));
|
||||
err.output.payload.message = err.message;
|
||||
}
|
||||
|
||||
err.reformat();
|
||||
return err;
|
||||
};
|
||||
|
||||
|
||||
internals.reformat = function (debug = false) {
|
||||
|
||||
this.output.payload.statusCode = this.output.statusCode;
|
||||
this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';
|
||||
|
||||
if (this.output.statusCode === 500 && debug !== true) {
|
||||
this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
|
||||
}
|
||||
else if (this.message) {
|
||||
this.output.payload.message = this.message;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
internals.serverError = function (message, data, statusCode, ctor) {
|
||||
|
||||
if (data instanceof Error &&
|
||||
!data.isBoom) {
|
||||
|
||||
return internals.Boom.boomify(data, { statusCode, message });
|
||||
}
|
||||
|
||||
return new internals.Boom(message, { statusCode, data, ctor });
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"boom@7.3.0",
|
||||
"C:\\Daten\\Git\\Tumortisch"
|
||||
]
|
||||
],
|
||||
"_from": "boom@7.3.0",
|
||||
"_id": "boom@7.3.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==",
|
||||
"_location": "/wreck/boom",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "boom@7.3.0",
|
||||
"name": "boom",
|
||||
"escapedName": "boom",
|
||||
"rawSpec": "7.3.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "7.3.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/wreck"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz",
|
||||
"_spec": "7.3.0",
|
||||
"_where": "C:\\Daten\\Git\\Tumortisch",
|
||||
"bugs": {
|
||||
"url": "https://github.com/hapijs/boom/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoek": "6.x.x"
|
||||
},
|
||||
"description": "HTTP-friendly error objects",
|
||||
"devDependencies": {
|
||||
"code": "5.x.x",
|
||||
"lab": "17.x.x",
|
||||
"markdown-toc": "0.12.x"
|
||||
},
|
||||
"homepage": "https://github.com/hapijs/boom#readme",
|
||||
"keywords": [
|
||||
"error",
|
||||
"http"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "lib/index.js",
|
||||
"name": "boom",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/hapijs/boom.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "lab -a code -t 100 -L",
|
||||
"test-cov-html": "lab -a code -r html -o coverage.html -L",
|
||||
"toc": "node generate-toc.js",
|
||||
"version": "npm run toc && git add README.md"
|
||||
},
|
||||
"version": "7.3.0"
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!lib/**
|
||||
!.npmignore
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/hoek/issues?q=is%3Aissue+label%3A%22release+notes%22).
|
||||
|
||||
If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/hoek/milestones?state=closed&direction=asc&sort=due_date).
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
Copyright (c) 2011-2018, Project contributors
|
||||
Copyright (c) 2011-2014, Walmart
|
||||
Copyright (c) 2011, Yahoo Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* The names of any contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||

|
||||
|
||||
Utility methods for the hapi ecosystem. This module is not intended to solve every problem for
|
||||
everyone, but rather as a central place to store hapi-specific methods. If you're looking for a
|
||||
general purpose utility module, check out [lodash](https://github.com/lodash/lodash) or
|
||||
[underscore](https://github.com/jashkenas/underscore).
|
||||
|
||||
[](http://travis-ci.org/hapijs/hoek)
|
||||
|
||||
Lead Maintainer: [Gil Pedersen](https://github.com/kanongil)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
[**API Reference**](API.md)
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
'use strict';
|
||||
|
||||
// Load modules
|
||||
|
||||
|
||||
// Declare internals
|
||||
|
||||
const internals = {
|
||||
arrayType: Symbol('array'),
|
||||
bufferType: Symbol('buffer'),
|
||||
dateType: Symbol('date'),
|
||||
errorType: Symbol('error'),
|
||||
genericType: Symbol('generic'),
|
||||
mapType: Symbol('map'),
|
||||
regexType: Symbol('regex'),
|
||||
setType: Symbol('set'),
|
||||
weakMapType: Symbol('weak-map'),
|
||||
weakSetType: Symbol('weak-set'),
|
||||
mismatched: Symbol('mismatched')
|
||||
};
|
||||
|
||||
|
||||
internals.typeMap = {
|
||||
'[object Array]': internals.arrayType,
|
||||
'[object Date]': internals.dateType,
|
||||
'[object Error]': internals.errorType,
|
||||
'[object Map]': internals.mapType,
|
||||
'[object RegExp]': internals.regexType,
|
||||
'[object Set]': internals.setType,
|
||||
'[object WeakMap]': internals.weakMapType,
|
||||
'[object WeakSet]': internals.weakSetType
|
||||
};
|
||||
|
||||
|
||||
internals.SeenEntry = class {
|
||||
|
||||
constructor(obj, ref) {
|
||||
|
||||
this.obj = obj;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
isSame(obj, ref) {
|
||||
|
||||
return this.obj === obj && this.ref === ref;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
internals.getInternalType = function (obj) {
|
||||
|
||||
const { typeMap, bufferType, genericType } = internals;
|
||||
|
||||
if (obj instanceof Buffer) {
|
||||
return bufferType;
|
||||
}
|
||||
|
||||
const objName = Object.prototype.toString.call(obj);
|
||||
return typeMap[objName] || genericType;
|
||||
};
|
||||
|
||||
|
||||
internals.getSharedType = function (obj, ref, checkPrototype) {
|
||||
|
||||
if (checkPrototype) {
|
||||
if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
|
||||
return internals.mismatched;
|
||||
}
|
||||
|
||||
return internals.getInternalType(obj);
|
||||
}
|
||||
|
||||
const type = internals.getInternalType(obj);
|
||||
if (type !== internals.getInternalType(ref)) {
|
||||
return internals.mismatched;
|
||||
}
|
||||
|
||||
return type;
|
||||
};
|
||||
|
||||
|
||||
internals.valueOf = function (obj) {
|
||||
|
||||
const objValueOf = obj.valueOf;
|
||||
if (objValueOf === undefined) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
try {
|
||||
return objValueOf.call(obj);
|
||||
}
|
||||
catch (err) {
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
internals.hasOwnEnumerableProperty = function (obj, key) {
|
||||
|
||||
return Object.prototype.propertyIsEnumerable.call(obj, key);
|
||||
};
|
||||
|
||||
|
||||
internals.isSetSimpleEqual = function (obj, ref) {
|
||||
|
||||
for (const entry of obj) {
|
||||
if (!ref.has(entry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
|
||||
|
||||
const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals;
|
||||
const { keys, getOwnPropertySymbols } = Object;
|
||||
|
||||
if (instanceType === internals.arrayType) {
|
||||
if (options.part) {
|
||||
// Check if any index match any other index
|
||||
|
||||
for (let i = 0; i < obj.length; ++i) {
|
||||
const objValue = obj[i];
|
||||
for (let j = 0; j < ref.length; ++j) {
|
||||
if (isDeepEqual(objValue, ref[j], options, seen)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (obj.length !== ref.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < obj.length; ++i) {
|
||||
if (!isDeepEqual(obj[i], ref[i], options, seen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (instanceType === internals.setType) {
|
||||
if (obj.size !== ref.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!internals.isSetSimpleEqual(obj, ref)) {
|
||||
|
||||
// Check for deep equality
|
||||
|
||||
const ref2 = new Set(ref);
|
||||
for (const objEntry of obj) {
|
||||
if (ref2.delete(objEntry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let found = false;
|
||||
for (const refEntry of ref2) {
|
||||
if (isDeepEqual(objEntry, refEntry, options, seen)) {
|
||||
ref2.delete(refEntry);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (instanceType === internals.mapType) {
|
||||
if (obj.size !== ref.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [key, value] of obj) {
|
||||
if (value === undefined && !ref.has(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDeepEqual(value, ref.get(key), options, seen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (instanceType === internals.errorType) {
|
||||
// Always check name and message
|
||||
|
||||
if (obj.name !== ref.name || obj.message !== ref.message) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check .valueOf()
|
||||
|
||||
const valueOfObj = valueOf(obj);
|
||||
const valueOfRef = valueOf(ref);
|
||||
if (!(obj === valueOfObj && ref === valueOfRef) &&
|
||||
!isDeepEqual(valueOfObj, valueOfRef, options, seen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check properties
|
||||
|
||||
const objKeys = keys(obj);
|
||||
if (!options.part && objKeys.length !== keys(ref).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < objKeys.length; ++i) {
|
||||
const key = objKeys[i];
|
||||
|
||||
if (!hasOwnEnumerableProperty(ref, key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDeepEqual(obj[key], ref[key], options, seen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check symbols
|
||||
|
||||
if (options.symbols) {
|
||||
const objSymbols = getOwnPropertySymbols(obj);
|
||||
const refSymbols = new Set(getOwnPropertySymbols(ref));
|
||||
|
||||
for (let i = 0; i < objSymbols.length; ++i) {
|
||||
const key = objSymbols[i];
|
||||
|
||||
if (hasOwnEnumerableProperty(obj, key)) {
|
||||
if (!hasOwnEnumerableProperty(ref, key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDeepEqual(obj[key], ref[key], options, seen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (hasOwnEnumerableProperty(ref, key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
refSymbols.delete(key);
|
||||
}
|
||||
|
||||
for (const key of refSymbols) {
|
||||
if (hasOwnEnumerableProperty(ref, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
internals.isDeepEqual = function (obj, ref, options, seen) {
|
||||
|
||||
if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
|
||||
return obj !== 0 || 1 / obj === 1 / ref;
|
||||
}
|
||||
|
||||
const type = typeof obj;
|
||||
|
||||
if (type !== typeof ref) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type !== 'object' ||
|
||||
obj === null ||
|
||||
ref === null) {
|
||||
|
||||
return obj !== obj && ref !== ref; // NaN
|
||||
}
|
||||
|
||||
const instanceType = internals.getSharedType(obj, ref, !!options.prototype);
|
||||
switch (instanceType) {
|
||||
case internals.bufferType:
|
||||
return Buffer.prototype.equals.call(obj, ref);
|
||||
case internals.regexType:
|
||||
return obj.toString() === ref.toString();
|
||||
case internals.mismatched:
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = seen.length - 1; i >= 0; --i) {
|
||||
if (seen[i].isSame(obj, ref)) {
|
||||
return true; // If previous comparison failed, it would have stopped execution
|
||||
}
|
||||
}
|
||||
|
||||
seen.push(new internals.SeenEntry(obj, ref));
|
||||
try {
|
||||
return !!internals.isDeepEqualObj(instanceType, obj, ref, options, seen);
|
||||
}
|
||||
finally {
|
||||
seen.pop();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = function (obj, ref, options) {
|
||||
|
||||
options = options || { prototype: true };
|
||||
|
||||
return !!internals.isDeepEqual(obj, ref, options, []);
|
||||
};
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
// Declare internals
|
||||
|
||||
const internals = {};
|
||||
|
||||
|
||||
exports.escapeHtml = function (input) {
|
||||
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let escaped = '';
|
||||
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
|
||||
const charCode = input.charCodeAt(i);
|
||||
|
||||
if (internals.isSafe(charCode)) {
|
||||
escaped += input[i];
|
||||
}
|
||||
else {
|
||||
escaped += internals.escapeHtmlChar(charCode);
|
||||
}
|
||||
}
|
||||
|
||||
return escaped;
|
||||
};
|
||||
|
||||
|
||||
exports.escapeJson = function (input) {
|
||||
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const lessThan = 0x3C;
|
||||
const greaterThan = 0x3E;
|
||||
const andSymbol = 0x26;
|
||||
const lineSeperator = 0x2028;
|
||||
|
||||
// replace method
|
||||
let charCode;
|
||||
return input.replace(/[<>&\u2028\u2029]/g, (match) => {
|
||||
|
||||
charCode = match.charCodeAt(0);
|
||||
|
||||
if (charCode === lessThan) {
|
||||
return '\\u003c';
|
||||
}
|
||||
|
||||
if (charCode === greaterThan) {
|
||||
return '\\u003e';
|
||||
}
|
||||
|
||||
if (charCode === andSymbol) {
|
||||
return '\\u0026';
|
||||
}
|
||||
|
||||
if (charCode === lineSeperator) {
|
||||
return '\\u2028';
|
||||
}
|
||||
|
||||
return '\\u2029';
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
internals.escapeHtmlChar = function (charCode) {
|
||||
|
||||
const namedEscape = internals.namedHtml[charCode];
|
||||
if (typeof namedEscape !== 'undefined') {
|
||||
return namedEscape;
|
||||
}
|
||||
|
||||
if (charCode >= 256) {
|
||||
return '&#' + charCode + ';';
|
||||
}
|
||||
|
||||
const hexValue = Buffer.from(String.fromCharCode(charCode), 'ascii').toString('hex');
|
||||
return `&#x${hexValue};`;
|
||||
};
|
||||
|
||||
|
||||
internals.isSafe = function (charCode) {
|
||||
|
||||
return (typeof internals.safeCharCodes[charCode] !== 'undefined');
|
||||
};
|
||||
|
||||
|
||||
internals.namedHtml = {
|
||||
'38': '&',
|
||||
'60': '<',
|
||||
'62': '>',
|
||||
'34': '"',
|
||||
'160': ' ',
|
||||
'162': '¢',
|
||||
'163': '£',
|
||||
'164': '¤',
|
||||
'169': '©',
|
||||
'174': '®'
|
||||
};
|
||||
|
||||
|
||||
internals.safeCharCodes = (function () {
|
||||
|
||||
const safe = {};
|
||||
|
||||
for (let i = 32; i < 123; ++i) {
|
||||
|
||||
if ((i >= 97) || // a-z
|
||||
(i >= 65 && i <= 90) || // A-Z
|
||||
(i >= 48 && i <= 57) || // 0-9
|
||||
i === 32 || // space
|
||||
i === 46 || // .
|
||||
i === 44 || // ,
|
||||
i === 45 || // -
|
||||
i === 58 || // :
|
||||
i === 95) { // _
|
||||
|
||||
safe[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return safe;
|
||||
}());
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
'use strict';
|
||||
|
||||
// Load modules
|
||||
|
||||
const Assert = require('assert');
|
||||
const Crypto = require('crypto');
|
||||
const Path = require('path');
|
||||
|
||||
const DeepEqual = require('./deep-equal');
|
||||
const Escape = require('./escape');
|
||||
|
||||
|
||||
// Declare internals
|
||||
|
||||
const internals = {};
|
||||
|
||||
|
||||
// Deep object or array comparison
|
||||
|
||||
exports.deepEqual = DeepEqual;
|
||||
|
||||
|
||||
// Clone object or array
|
||||
|
||||
exports.clone = function (obj, options = {}, _seen = null) {
|
||||
|
||||
if (typeof obj !== 'object' ||
|
||||
obj === null) {
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
const seen = _seen || new Map();
|
||||
|
||||
const lookup = seen.get(obj);
|
||||
if (lookup) {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
let newObj;
|
||||
let cloneDeep = false;
|
||||
const isArray = Array.isArray(obj);
|
||||
|
||||
if (!isArray) {
|
||||
if (Buffer.isBuffer(obj)) {
|
||||
newObj = Buffer.from(obj);
|
||||
}
|
||||
else if (obj instanceof Date) {
|
||||
newObj = new Date(obj.getTime());
|
||||
}
|
||||
else if (obj instanceof RegExp) {
|
||||
newObj = new RegExp(obj);
|
||||
}
|
||||
else {
|
||||
if (options.prototype !== false) { // Defaults to true
|
||||
const proto = Object.getPrototypeOf(obj);
|
||||
if (proto &&
|
||||
proto.isImmutable) {
|
||||
|
||||
newObj = obj;
|
||||
}
|
||||
else {
|
||||
newObj = Object.create(proto);
|
||||
cloneDeep = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newObj = {};
|
||||
cloneDeep = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
newObj = [];
|
||||
cloneDeep = true;
|
||||
}
|
||||
|
||||
seen.set(obj, newObj);
|
||||
|
||||
if (cloneDeep) {
|
||||
const keys = internals.keys(obj, options);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const key = keys[i];
|
||||
|
||||
if (isArray && key === 'length') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||||
if (descriptor &&
|
||||
(descriptor.get ||
|
||||
descriptor.set)) {
|
||||
|
||||
Object.defineProperty(newObj, key, descriptor);
|
||||
}
|
||||
else {
|
||||
Object.defineProperty(newObj, key, {
|
||||
enumerable: descriptor ? descriptor.enumerable : true,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: exports.clone(obj[key], options, seen)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray) {
|
||||
newObj.length = obj.length;
|
||||
}
|
||||
}
|
||||
|
||||
return newObj;
|
||||
};
|
||||
|
||||
|
||||
internals.keys = function (obj, options = {}) {
|
||||
|
||||
return options.symbols ? Reflect.ownKeys(obj) : Object.getOwnPropertyNames(obj);
|
||||
};
|
||||
|
||||
|
||||
// Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied
|
||||
|
||||
exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
|
||||
|
||||
exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object');
|
||||
exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
|
||||
|
||||
if (!source) {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (Array.isArray(source)) {
|
||||
exports.assert(Array.isArray(target), 'Cannot merge array onto an object');
|
||||
if (isMergeArrays === false) { // isMergeArrays defaults to true
|
||||
target.length = 0; // Must not change target assignment
|
||||
}
|
||||
|
||||
for (let i = 0; i < source.length; ++i) {
|
||||
target.push(exports.clone(source[i]));
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
const keys = internals.keys(source);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const key = keys[i];
|
||||
if (key === '__proto__' ||
|
||||
!Object.prototype.propertyIsEnumerable.call(source, key)) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = source[key];
|
||||
if (value &&
|
||||
typeof value === 'object') {
|
||||
|
||||
if (!target[key] ||
|
||||
typeof target[key] !== 'object' ||
|
||||
(Array.isArray(target[key]) !== Array.isArray(value)) ||
|
||||
value instanceof Date ||
|
||||
Buffer.isBuffer(value) ||
|
||||
value instanceof RegExp) {
|
||||
|
||||
target[key] = exports.clone(value);
|
||||
}
|
||||
else {
|
||||
exports.merge(target[key], value, isNullOverride, isMergeArrays);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (value !== null &&
|
||||
value !== undefined) { // Explicit to preserve empty strings
|
||||
|
||||
target[key] = value;
|
||||
}
|
||||
else if (isNullOverride !== false) { // Defaults to true
|
||||
target[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
|
||||
// Apply options to a copy of the defaults
|
||||
|
||||
exports.applyToDefaults = function (defaults, options, isNullOverride) {
|
||||
|
||||
exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
|
||||
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
|
||||
|
||||
if (!options) { // If no options, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const copy = exports.clone(defaults);
|
||||
|
||||
if (options === true) { // If options is set to true, use defaults
|
||||
return copy;
|
||||
}
|
||||
|
||||
return exports.merge(copy, options, isNullOverride === true, false);
|
||||
};
|
||||
|
||||
|
||||
// Clone an object except for the listed keys which are shallow copied
|
||||
|
||||
exports.cloneWithShallow = function (source, keys, options) {
|
||||
|
||||
if (!source ||
|
||||
typeof source !== 'object') {
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
const storage = internals.store(source, keys); // Move shallow copy items to storage
|
||||
const copy = exports.clone(source, options); // Deep copy the rest
|
||||
internals.restore(copy, source, storage); // Shallow copy the stored items and restore
|
||||
return copy;
|
||||
};
|
||||
|
||||
|
||||
internals.store = function (source, keys) {
|
||||
|
||||
const storage = new Map();
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const key = keys[i];
|
||||
const value = exports.reach(source, key);
|
||||
if (typeof value === 'object' ||
|
||||
typeof value === 'function') {
|
||||
|
||||
storage.set(key, value);
|
||||
internals.reachSet(source, key, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return storage;
|
||||
};
|
||||
|
||||
|
||||
internals.restore = function (copy, source, storage) {
|
||||
|
||||
for (const [key, value] of storage) {
|
||||
internals.reachSet(copy, key, value);
|
||||
internals.reachSet(source, key, value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
internals.reachSet = function (obj, key, value) {
|
||||
|
||||
const path = Array.isArray(key) ? key : key.split('.');
|
||||
let ref = obj;
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
const segment = path[i];
|
||||
if (i + 1 === path.length) {
|
||||
ref[segment] = value;
|
||||
}
|
||||
|
||||
ref = ref[segment];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Apply options to defaults except for the listed keys which are shallow copied from option without merging
|
||||
|
||||
exports.applyToDefaultsWithShallow = function (defaults, options, keys) {
|
||||
|
||||
exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
|
||||
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
|
||||
exports.assert(keys && Array.isArray(keys), 'Invalid keys');
|
||||
|
||||
if (!options) { // If no options, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const copy = exports.cloneWithShallow(defaults, keys);
|
||||
|
||||
if (options === true) { // If options is set to true, use defaults
|
||||
return copy;
|
||||
}
|
||||
|
||||
const storage = internals.store(options, keys); // Move shallow copy items to storage
|
||||
exports.merge(copy, options, false, false); // Deep copy the rest
|
||||
internals.restore(copy, options, storage); // Shallow copy the stored items and restore
|
||||
return copy;
|
||||
};
|
||||
|
||||
|
||||
// Find the common unique items in two arrays
|
||||
|
||||
exports.intersect = function (array1, array2, justFirst) {
|
||||
|
||||
if (!array1 ||
|
||||
!array2) {
|
||||
|
||||
return (justFirst ? null : []);
|
||||
}
|
||||
|
||||
const common = [];
|
||||
const hash = (Array.isArray(array1) ? new Set(array1) : array1);
|
||||
const found = new Set();
|
||||
for (const value of array2) {
|
||||
if (internals.has(hash, value) &&
|
||||
!found.has(value)) {
|
||||
|
||||
if (justFirst) {
|
||||
return value;
|
||||
}
|
||||
|
||||
common.push(value);
|
||||
found.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return (justFirst ? null : common);
|
||||
};
|
||||
|
||||
|
||||
internals.has = function (ref, key) {
|
||||
|
||||
if (typeof ref.has === 'function') {
|
||||
return ref.has(key);
|
||||
}
|
||||
|
||||
return ref[key] !== undefined;
|
||||
};
|
||||
|
||||
|
||||
// Test if the reference contains the values
|
||||
|
||||
exports.contain = function (ref, values, options = {}) { // options: { deep, once, only, part, symbols }
|
||||
|
||||
/*
|
||||
string -> string(s)
|
||||
array -> item(s)
|
||||
object -> key(s)
|
||||
object -> object (key:value)
|
||||
*/
|
||||
|
||||
let valuePairs = null;
|
||||
if (typeof ref === 'object' &&
|
||||
typeof values === 'object' &&
|
||||
!Array.isArray(ref) &&
|
||||
!Array.isArray(values)) {
|
||||
|
||||
valuePairs = values;
|
||||
const symbols = Object.getOwnPropertySymbols(values).filter(Object.prototype.propertyIsEnumerable.bind(values));
|
||||
values = [...Object.keys(values), ...symbols];
|
||||
}
|
||||
else {
|
||||
values = [].concat(values);
|
||||
}
|
||||
|
||||
exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object');
|
||||
exports.assert(values.length, 'Values array cannot be empty');
|
||||
|
||||
let compare;
|
||||
let compareFlags;
|
||||
if (options.deep) {
|
||||
compare = exports.deepEqual;
|
||||
|
||||
const hasOnly = options.hasOwnProperty('only');
|
||||
const hasPart = options.hasOwnProperty('part');
|
||||
|
||||
compareFlags = {
|
||||
prototype: hasOnly ? options.only : hasPart ? !options.part : false,
|
||||
part: hasOnly ? !options.only : hasPart ? options.part : false
|
||||
};
|
||||
}
|
||||
else {
|
||||
compare = (a, b) => a === b;
|
||||
}
|
||||
|
||||
let misses = false;
|
||||
const matches = new Array(values.length);
|
||||
for (let i = 0; i < matches.length; ++i) {
|
||||
matches[i] = 0;
|
||||
}
|
||||
|
||||
if (typeof ref === 'string') {
|
||||
let pattern = '(';
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
const value = values[i];
|
||||
exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
|
||||
pattern += (i ? '|' : '') + exports.escapeRegex(value);
|
||||
}
|
||||
|
||||
const regex = new RegExp(pattern + ')', 'g');
|
||||
const leftovers = ref.replace(regex, ($0, $1) => {
|
||||
|
||||
const index = values.indexOf($1);
|
||||
++matches[index];
|
||||
return ''; // Remove from string
|
||||
});
|
||||
|
||||
misses = !!leftovers;
|
||||
}
|
||||
else if (Array.isArray(ref)) {
|
||||
const onlyOnce = !!(options.only && options.once);
|
||||
if (onlyOnce && ref.length !== values.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < ref.length; ++i) {
|
||||
let matched = false;
|
||||
for (let j = 0; j < values.length && matched === false; ++j) {
|
||||
if (!onlyOnce || matches[j] === 0) {
|
||||
matched = compare(values[j], ref[i], compareFlags) && j;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched !== false) {
|
||||
++matches[matched];
|
||||
}
|
||||
else {
|
||||
misses = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const keys = internals.keys(ref, options);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
const key = keys[i];
|
||||
const pos = values.indexOf(key);
|
||||
if (pos !== -1) {
|
||||
if (valuePairs &&
|
||||
!compare(valuePairs[key], ref[key], compareFlags)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
++matches[pos];
|
||||
}
|
||||
else {
|
||||
misses = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.only) {
|
||||
if (misses || !options.once) {
|
||||
return !misses;
|
||||
}
|
||||
}
|
||||
|
||||
let result = false;
|
||||
for (let i = 0; i < matches.length; ++i) {
|
||||
result = result || !!matches[i];
|
||||
if ((options.once && matches[i] > 1) ||
|
||||
(!options.part && !matches[i])) {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// Flatten array
|
||||
|
||||
exports.flatten = function (array, target) {
|
||||
|
||||
const result = target || [];
|
||||
|
||||
for (let i = 0; i < array.length; ++i) {
|
||||
if (Array.isArray(array[i])) {
|
||||
exports.flatten(array[i], result);
|
||||
}
|
||||
else {
|
||||
result.push(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
|
||||
|
||||
exports.reach = function (obj, chain, options) {
|
||||
|
||||
if (chain === false ||
|
||||
chain === null ||
|
||||
typeof chain === 'undefined') {
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
if (typeof options === 'string') {
|
||||
options = { separator: options };
|
||||
}
|
||||
|
||||
const isChainArray = Array.isArray(chain);
|
||||
|
||||
exports.assert(!isChainArray || !options.separator, 'Separator option no valid for array-based chain');
|
||||
|
||||
const path = isChainArray ? chain : chain.split(options.separator || '.');
|
||||
let ref = obj;
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
let key = path[i];
|
||||
|
||||
if (Array.isArray(ref)) {
|
||||
const number = Number(key);
|
||||
|
||||
if (Number.isInteger(number) && number < 0) {
|
||||
key = ref.length + number;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ref ||
|
||||
!((typeof ref === 'object' || typeof ref === 'function') && key in ref) ||
|
||||
(typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties
|
||||
|
||||
exports.assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain);
|
||||
exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain);
|
||||
ref = options.default;
|
||||
break;
|
||||
}
|
||||
|
||||
ref = ref[key];
|
||||
}
|
||||
|
||||
return ref;
|
||||
};
|
||||
|
||||
|
||||
exports.reachTemplate = function (obj, template, options) {
|
||||
|
||||
return template.replace(/{([^}]+)}/g, ($0, chain) => {
|
||||
|
||||
const value = exports.reach(obj, chain, options);
|
||||
return (value === undefined || value === null ? '' : value);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.assert = function (condition, ...args) {
|
||||
|
||||
if (condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length === 1 && args[0] instanceof Error) {
|
||||
throw args[0];
|
||||
}
|
||||
|
||||
const msgs = args
|
||||
.filter((arg) => arg !== '')
|
||||
.map((arg) => {
|
||||
|
||||
return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : exports.stringify(arg);
|
||||
});
|
||||
|
||||
throw new Assert.AssertionError({
|
||||
message: msgs.join(' ') || 'Unknown error',
|
||||
actual: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
stackStartFunction: exports.assert
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.Bench = function () {
|
||||
|
||||
this.ts = 0;
|
||||
this.reset();
|
||||
};
|
||||
|
||||
|
||||
exports.Bench.prototype.reset = function () {
|
||||
|
||||
this.ts = exports.Bench.now();
|
||||
};
|
||||
|
||||
|
||||
exports.Bench.prototype.elapsed = function () {
|
||||
|
||||
return exports.Bench.now() - this.ts;
|
||||
};
|
||||
|
||||
|
||||
exports.Bench.now = function () {
|
||||
|
||||
const ts = process.hrtime();
|
||||
return (ts[0] * 1e3) + (ts[1] / 1e6);
|
||||
};
|
||||
|
||||
|
||||
// Escape string for Regex construction
|
||||
|
||||
exports.escapeRegex = function (string) {
|
||||
|
||||
// Escape ^$.*+-?=!:|\/()[]{},
|
||||
return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
|
||||
};
|
||||
|
||||
|
||||
// Escape attribute value for use in HTTP header
|
||||
|
||||
exports.escapeHeaderAttribute = function (attribute) {
|
||||
|
||||
// Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
|
||||
|
||||
exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')');
|
||||
|
||||
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
|
||||
};
|
||||
|
||||
|
||||
exports.escapeHtml = function (string) {
|
||||
|
||||
return Escape.escapeHtml(string);
|
||||
};
|
||||
|
||||
|
||||
exports.escapeJson = function (string) {
|
||||
|
||||
return Escape.escapeJson(string);
|
||||
};
|
||||
|
||||
|
||||
exports.once = function (method) {
|
||||
|
||||
if (method._hoekOnce) {
|
||||
return method;
|
||||
}
|
||||
|
||||
let once = false;
|
||||
const wrapped = function (...args) {
|
||||
|
||||
if (!once) {
|
||||
once = true;
|
||||
method(...args);
|
||||
}
|
||||
};
|
||||
|
||||
wrapped._hoekOnce = true;
|
||||
return wrapped;
|
||||
};
|
||||
|
||||
|
||||
exports.ignore = function () { };
|
||||
|
||||
|
||||
exports.uniqueFilename = function (path, extension) {
|
||||
|
||||
if (extension) {
|
||||
extension = extension[0] !== '.' ? '.' + extension : extension;
|
||||
}
|
||||
else {
|
||||
extension = '';
|
||||
}
|
||||
|
||||
path = Path.resolve(path);
|
||||
const name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension;
|
||||
return Path.join(path, name);
|
||||
};
|
||||
|
||||
|
||||
exports.stringify = function (...args) {
|
||||
|
||||
try {
|
||||
return JSON.stringify.apply(null, args);
|
||||
}
|
||||
catch (err) {
|
||||
return '[Cannot display object: ' + err.message + ']';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.wait = function (timeout) {
|
||||
|
||||
return new Promise((resolve) => setTimeout(resolve, timeout));
|
||||
};
|
||||
|
||||
|
||||
exports.block = function () {
|
||||
|
||||
return new Promise(exports.ignore);
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"hoek@6.1.3",
|
||||
"C:\\Daten\\Git\\Tumortisch"
|
||||
]
|
||||
],
|
||||
"_from": "hoek@6.1.3",
|
||||
"_id": "hoek@6.1.3",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==",
|
||||
"_location": "/wreck/hoek",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "hoek@6.1.3",
|
||||
"name": "hoek",
|
||||
"escapedName": "hoek",
|
||||
"rawSpec": "6.1.3",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "6.1.3"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/wreck",
|
||||
"/wreck/boom"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz",
|
||||
"_spec": "6.1.3",
|
||||
"_where": "C:\\Daten\\Git\\Tumortisch",
|
||||
"bugs": {
|
||||
"url": "https://github.com/hapijs/hoek/issues"
|
||||
},
|
||||
"dependencies": {},
|
||||
"description": "General purpose node utilities",
|
||||
"devDependencies": {
|
||||
"code": "5.x.x",
|
||||
"lab": "18.x.x"
|
||||
},
|
||||
"homepage": "https://github.com/hapijs/hoek#readme",
|
||||
"keywords": [
|
||||
"utilities"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "lib/index.js",
|
||||
"name": "hoek",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/hapijs/hoek.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "lab -a code -t 100 -L",
|
||||
"test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html"
|
||||
},
|
||||
"version": "6.1.3"
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"wreck@14.2.0",
|
||||
"C:\\Daten\\Git\\Tumortisch"
|
||||
]
|
||||
],
|
||||
"_from": "wreck@14.2.0",
|
||||
"_id": "wreck@14.2.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-NFFft3SMgqrJbXEVfYifh+QDWFxni+98/I7ut7rLbz3F0XOypluHsdo3mdEYssGSirMobM3fGlqhyikbWKDn2Q==",
|
||||
"_location": "/wreck",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "wreck@14.2.0",
|
||||
"name": "wreck",
|
||||
"escapedName": "wreck",
|
||||
"rawSpec": "14.2.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "14.2.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/simple-oauth2"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/wreck/-/wreck-14.2.0.tgz",
|
||||
"_spec": "14.2.0",
|
||||
"_where": "C:\\Daten\\Git\\Tumortisch",
|
||||
"bugs": {
|
||||
"url": "https://github.com/hapijs/wreck/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"boom": "7.x.x",
|
||||
"bourne": "1.x.x",
|
||||
"hoek": "6.x.x"
|
||||
},
|
||||
"description": "HTTP Client Utilities",
|
||||
"devDependencies": {
|
||||
"code": "5.x.x",
|
||||
"eslint-plugin-markdown": "1.0.0",
|
||||
"lab": "18.x.x"
|
||||
},
|
||||
"homepage": "https://github.com/hapijs/wreck#readme",
|
||||
"keywords": [
|
||||
"utilities",
|
||||
"http",
|
||||
"client"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "lib/index",
|
||||
"name": "wreck",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/hapijs/wreck.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint-md": "eslint --config hapi --parser-options=ecmaVersion:8 --rule 'strict: 0, eol-last: 0' --plugin markdown --ext md .",
|
||||
"posttest": "npm run lint-md",
|
||||
"test": "lab -t 100 -L -a code",
|
||||
"test-cov-html": "lab -r html -o coverage.html -a code"
|
||||
},
|
||||
"version": "14.2.0"
|
||||
}
|
||||
Reference in New Issue
Block a user