diff --git a/advanced-creation.md b/advanced-creation.md
index a03bce256..97ca9a642 100644
--- a/advanced-creation.md
+++ b/advanced-creation.md
@@ -54,7 +54,7 @@ const settings = {
return next(options);
},
options: got.mergeOptions(got.defaults.options, {
- json: true
+ responseType: 'json'
})
};
@@ -110,9 +110,10 @@ const defaults = {
followRedirect: true,
stream: false,
form: false,
- json: false,
cache: false,
- useElectronNet: false
+ useElectronNet: false,
+ responseType: 'text',
+ resolveBodyOnly: 'false'
},
mutableDefaults: false
};
diff --git a/migration-guides.md b/migration-guides.md
index 4f05b90ec..7168d7d56 100644
--- a/migration-guides.md
+++ b/migration-guides.md
@@ -44,7 +44,6 @@ These Got options are the same as with Request:
- [`url`](https://github.com/sindresorhus/got#url) (+ we accept [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instances too!)
- [`body`](https://github.com/sindresorhus/got#body)
-- [`json`](https://github.com/sindresorhus/got#json)
- [`followRedirect`](https://github.com/sindresorhus/got#followRedirect)
- [`encoding`](https://github.com/sindresorhus/got#encoding)
@@ -77,6 +76,7 @@ To use streams, just call `got.stream(url, options)` or `got(url, {stream: true,
#### Breaking changes
+- The `json` option is not a `boolean`, it's an `Object`. It will be stringified and used as a body.
- No `form` option. You have to pass a [`form-data` instance](https://github.com/form-data/form-data) through the [`body` option](https://github.com/sindresorhus/got#body).
- No `oauth`/`hawk`/`aws`/`httpSignature` option. To sign requests, you need to create a [custom instance](advanced-creation.md#signing-requests).
- No `agentClass`/`agentOptions`/`pool` option.
@@ -105,21 +105,17 @@ const gotInstance = got.extend({
hooks: {
init: [
options => {
- // Save the original option, so we can look at it in the `afterResponse` hook
- options.originalJson = options.json;
-
- if (options.json && options.jsonReplacer) {
+ if (options.jsonReplacer) {
options.body = JSON.stringify(options.body, options.jsonReplacer);
- options.json = false; // We've handled that on our own
}
}
],
afterResponse: [
response => {
const options = response.request.gotOptions;
- if (options.originalJson && options.jsonReviver) {
+ if (options.jsonReviver && options.responseType === 'json') {
+ options.responseType = '';
response.body = JSON.parse(response.body, options.jsonReviver);
- options.json = false; // We've handled that on our own
}
return response;
diff --git a/readme.md b/readme.md
index c0553c416..9890dee8c 100644
--- a/readme.md
+++ b/readme.md
@@ -35,7 +35,7 @@ Got is for Node.js. For browsers, we recommend [Ky](https://github.com/sindresor
- [Handles gzip/deflate](#decompress)
- [Timeout handling](#timeout)
- [Errors with metadata](#errors)
-- [JSON mode](#json)
+- [JSON mode](#json-mode)
- [WHATWG URL support](#url)
- [Hooks](#hooks)
- [Instances with custom defaults](#instances)
@@ -157,14 +157,44 @@ Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.st
Type: `string` `Buffer` `stream.Readable` [`form-data` instance](https://github.com/form-data/form-data)
-**Note:** If you provide this option, `got.stream()` will be read-only.
+**Note:** The `body` option cannot be used with the `json` or `form` option.
-The body that will be sent with a `POST` request.
+**Note:** If you provide this option, `got.stream()` will be read-only.
If present in `options` and `options.method` is not set, `options.method` will be set to `POST`.
The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / `fs.createReadStream` instance / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
+###### json
+
+Type: `Object` `Array` `number` `string` `boolean` `null`
+
+**Note:** If you provide this option, `got.stream()` will be read-only.
+
+JSON body. The `Content-Type` header will be set to `application/json` if it's not defined.
+
+###### responseType
+
+Type: `string`
+Default: `text`
+
+**Note:** When using streams, this option is ignored.
+
+Parsing method used to retrieve the body from the response. Can be `text`, `json` or `buffer`. The promise has `.json()` and `.buffer()` and `.text()` functions which set this option automatically.
+
+Example:
+
+```js
+const {body} = await got(url).json();
+```
+
+###### resolveBodyOnly
+
+Type: `string`
+Default: `false`
+
+When set to `true` the promise will return the [Response body](#body-1) instead of the [Response](#response) object.
+
###### cookieJar
Type: [`tough.CookieJar` instance](https://github.com/salesforce/tough-cookie#cookiejar)
@@ -182,25 +212,13 @@ Default: `'utf8'`
###### form
-Type: `boolean`
-Default: `false`
+Type: `Object`
**Note:** If you provide this option, `got.stream()` will be read-only.
-**Note:** `body` must be a plain object. It will be converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj).
-If set to `true` and `Content-Type` header is not set, it will be set to `application/x-www-form-urlencoded`.
+The form body is converted to query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj).
-###### json
-
-Type: `boolean`
-Default: `false`
-
-**Note:** If you use `got.stream()`, this option will be ignored.
-**Note:** `body` must be a plain object or array and will be stringified.
-
-If set to `true` and `Content-Type` header is not set, it will be set to `application/json`.
-
-Parse response body with `JSON.parse` and set `accept` header to `application/json`. If used in conjunction with the `form` option, the `body` will the stringified as querystring and the response parsed as JSON.
+If set to `true` and `Content-Type` header is not set, it will be set to `application/x-www-form-urlencoded`.
###### searchParams
@@ -364,19 +382,17 @@ Called with plain [request options](#options), right before their normalization.
See the [Request migration guide](migration-guides.md#breaking-changes) for an example.
-**Note**: This hook must be synchronous!
+**Note:** This hook must be synchronous!
###### hooks.beforeRequest
Type: `Function[]`
Default: `[]`
-Called with [normalized](source/normalize-arguments.js) [request options](#options). Got will make no further changes to the request before it is sent. This is especially useful in conjunction with [`got.extend()`](#instances) and [`got.create()`](advanced-creation.md) when you want to create an API client that, for example, uses HMAC-signing.
+Called with [normalized](source/normalize-arguments.js) [request options](#options). Got will make no further changes to the request before it is sent (except the body serialization). This is especially useful in conjunction with [`got.extend()`](#instances) and [`got.create()`](advanced-creation.md) when you want to create an API client that, for example, uses HMAC-signing.
See the [AWS section](#aws) for an example.
-**Note:** If you modify the `body` you will need to modify the `content-length` header too, because it has already been computed and assigned.
-
###### hooks.beforeRedirect
Type: `Function[]`
@@ -469,7 +485,7 @@ Default: `[]`
Called with an `Error` instance. The error is passed to the hook right before it's thrown. This is especially useful when you want to have more detailed errors.
-**Note**: Errors thrown while normalizing input options are thrown directly and not part of this hook.
+**Note:** Errors thrown while normalizing input options are thrown directly and not part of this hook.
```js
const got = require('got');
@@ -505,7 +521,7 @@ Type: `Object`
##### body
-Type: `string` `Object` *(depending on `options.json`)*
+Type: `string` `Object` `Buffer` *(depending on `options.responseType`)*
The result of the request.
@@ -666,11 +682,11 @@ client.get('/demo');
'x-foo': 'bar'
}
});
- const {headers} = (await client.get('/headers', {json: true})).body;
+ const {headers} = (await client.get('/headers').json()).body;
//=> headers['x-foo'] === 'bar'
const jsonClient = client.extend({
- json: true,
+ responseType: 'json',
headers: {
'x-baz': 'qux'
}
@@ -731,7 +747,7 @@ When reading from response stream fails.
#### got.ParseError
-When `json` option is enabled, server response code is 2xx, and `JSON.parse` fails. Includes `statusCode` and `statusMessage` properties.
+When server response code is 2xx, and parsing body fails. Includes `body`, `statusCode` and `statusMessage` properties.
#### got.HTTPError
@@ -917,7 +933,7 @@ const url = 'https://api.twitter.com/1.1/statuses/home_timeline.json';
got(url, {
headers: oauth.toHeader(oauth.authorize({url, method: 'GET'}, token)),
- json: true
+ responseType: 'json'
});
```
@@ -1008,6 +1024,43 @@ const createTestServer = require('create-test-server');
## Tips
+### JSON mode
+
+By default, if you pass an object to the `body` option it will be stringified using `JSON.stringify`. Example:
+
+```js
+const got = require('got');
+
+(async () => {
+ const response = await got('httpbin.org/anything', {
+ body: {
+ hello: 'world'
+ },
+ responseType: 'json'
+ });
+
+ console.log(response.body.data);
+ //=> '{"hello":"world"}'
+})();
+```
+
+To receive a JSON body you can either set `responseType` option to `json` or use `promise.json()`. Example:
+
+```js
+const got = require('got');
+
+(async () => {
+ const {body} = await got('httpbin.org/anything', {
+ body: {
+ hello: 'world'
+ }
+ }).json();
+
+ console.log(body);
+ //=> {...}
+})();
+```
+
### User Agent
It's a good idea to set the `'user-agent'` header so the provider can more easily see how their resource is used. By default, it's the URL to this repo. You can omit this header by setting it to `null`.
@@ -1045,7 +1098,7 @@ const pkg = require('./package.json');
const custom = got.extend({
baseUrl: 'example.com',
- json: true,
+ responseType: 'json',
headers: {
'user-agent': `my-package/${pkg.version} (https://github.com/username/my-package)`
}
@@ -1094,7 +1147,7 @@ const h2got = got.extend({request});
| Advanced timeouts | ✔ | ✖ | ✖ | ✖ | ✖ |
| Timings | ✔ | ✔ | ✖ | ✖ | ✖ |
| Errors with metadata | ✔ | ✖ | ✖ | ✔ | ✖ |
-| JSON mode | ✔ | ✔ | ✖ | ✔ | ✔ |
+| JSON mode | ✔ | ✔ | ✔ | ✔ | ✔ |
| Custom defaults | ✔ | ✔ | ✖ | ✔ | ✖ |
| Composable | ✔ | ✖ | ✖ | ✖ | ✔ |
| Hooks | ✔ | ✖ | ✖ | ✔ | ✖ |
diff --git a/source/as-promise.js b/source/as-promise.js
index c5023253f..5de69324c 100644
--- a/source/as-promise.js
+++ b/source/as-promise.js
@@ -11,6 +11,16 @@ const {reNormalize} = require('./normalize-arguments');
const asPromise = options => {
const proxy = new EventEmitter();
+ const parseBody = response => {
+ if (options.responseType === 'json') {
+ response.body = JSON.parse(response.body);
+ } else if (options.responseType === 'buffer') {
+ response.body = Buffer.from(response.body);
+ } else if (options.responseType !== 'text' && !is.falsy(options.responseType)) {
+ throw new Error(`Failed to parse body of type '${options.responseType}'`);
+ }
+ };
+
const promise = new PCancelable((resolve, reject, onCancel) => {
const emitter = requestAsEventEmitter(options);
@@ -57,9 +67,9 @@ const asPromise = options => {
const {statusCode} = response;
- if (options.json && response.body) {
+ if (response.body) {
try {
- response.body = JSON.parse(response.body);
+ parseBody(response);
} catch (error) {
if (statusCode >= 200 && statusCode < 300) {
const parseError = new ParseError(error, statusCode, options, data);
@@ -79,13 +89,13 @@ const asPromise = options => {
return;
}
- resolve(response);
+ resolve(options.resolveBodyOnly ? response.body : response);
}
return;
}
- resolve(response);
+ resolve(options.resolveBodyOnly ? response.body : response);
});
emitter.once('error', reject);
@@ -102,6 +112,24 @@ const asPromise = options => {
return promise;
};
+ promise.json = () => {
+ options.responseType = 'json';
+ options.resolveBodyOnly = true;
+ return promise;
+ };
+
+ promise.buffer = () => {
+ options.responseType = 'buffer';
+ options.resolveBodyOnly = true;
+ return promise;
+ };
+
+ promise.text = () => {
+ options.responseType = 'text';
+ options.resolveBodyOnly = true;
+ return promise;
+ };
+
return promise;
};
diff --git a/source/errors.js b/source/errors.js
index b6cbadc3c..74a42343a 100644
--- a/source/errors.js
+++ b/source/errors.js
@@ -52,8 +52,9 @@ module.exports.ReadError = class extends GotError {
module.exports.ParseError = class extends GotError {
constructor(error, statusCode, options, data) {
- super(`${error.message} in "${urlLib.format(options)}": \n${data.slice(0, 77)}...`, error, options);
+ super(`${error.message} in "${urlLib.format(options)}"`, error, options);
this.name = 'ParseError';
+ this.body = data;
this.statusCode = statusCode;
this.statusMessage = http.STATUS_CODES[this.statusCode];
}
diff --git a/source/index.js b/source/index.js
index cbf7c3739..f13d87440 100644
--- a/source/index.js
+++ b/source/index.js
@@ -47,10 +47,10 @@ const defaults = {
throwHttpErrors: true,
followRedirect: true,
stream: false,
- form: false,
- json: false,
cache: false,
- useElectronNet: false
+ useElectronNet: false,
+ responseType: 'text',
+ resolveBodyOnly: false
},
mutableDefaults: false
};
diff --git a/source/normalize-arguments.js b/source/normalize-arguments.js
index 29a1916c7..be461e7ff 100644
--- a/source/normalize-arguments.js
+++ b/source/normalize-arguments.js
@@ -5,7 +5,6 @@ const is = require('@sindresorhus/is');
const urlParseLax = require('url-parse-lax');
const lowercaseKeys = require('lowercase-keys');
const urlToOptions = require('./utils/url-to-options');
-const isFormData = require('./utils/is-form-data').default;
const validateSearchParams = require('./utils/validate-search-params');
const merge = require('./merge');
const knownHookEvents = require('./known-hook-events');
@@ -34,10 +33,6 @@ const preNormalize = (options, defaults) => {
options.baseUrl += '/';
}
- if (options.stream) {
- options.json = false;
- }
-
if (is.nullOrUndefined(options.hooks)) {
options.hooks = {};
} else if (!is.object(options.hooks)) {
@@ -201,43 +196,14 @@ const normalize = (url, options, defaults) => {
}
}
- if (options.json && is.undefined(headers.accept)) {
- headers.accept = 'application/json';
- }
-
if (options.decompress && is.undefined(headers['accept-encoding'])) {
headers['accept-encoding'] = 'gzip, deflate';
}
- const {body} = options;
- if (is.nullOrUndefined(body)) {
- options.method = options.method ? options.method.toUpperCase() : 'GET';
+ if (options.method) {
+ options.method = options.method.toUpperCase();
} else {
- const isObject = is.object(body) && !is.buffer(body) && !is.nodeStream(body);
- if (!is.nodeStream(body) && !is.string(body) && !is.buffer(body) && !(options.form || options.json)) {
- throw new TypeError('The `body` option must be a stream.Readable, string or Buffer');
- }
-
- if (options.json && !(isObject || is.array(body))) {
- throw new TypeError('The `body` option must be an Object or Array when the `json` option is used');
- }
-
- if (options.form && !isObject) {
- throw new TypeError('The `body` option must be an Object when the `form` option is used');
- }
-
- if (isFormData(body)) {
- // Special case for https://github.com/form-data/form-data
- headers['content-type'] = headers['content-type'] || `multipart/form-data; boundary=${body.getBoundary()}`;
- } else if (options.form) {
- headers['content-type'] = headers['content-type'] || 'application/x-www-form-urlencoded';
- options.body = (new URLSearchParams(body)).toString();
- } else if (options.json) {
- headers['content-type'] = headers['content-type'] || 'application/json';
- options.body = JSON.stringify(body);
- }
-
- options.method = options.method ? options.method.toUpperCase() : 'POST';
+ options.method = is.nullOrUndefined(options.body) ? 'GET' : 'POST';
}
if (!is.function(options.retry.retries)) {
diff --git a/source/request-as-event-emitter.js b/source/request-as-event-emitter.js
index 79586af84..892200852 100644
--- a/source/request-as-event-emitter.js
+++ b/source/request-as-event-emitter.js
@@ -1,5 +1,5 @@
'use strict';
-const {URL} = require('url'); // TODO: Use the `URL` global when targeting Node.js 10
+const {URL, URLSearchParams} = require('url'); // TODO: Use the `URL` global when targeting Node.js 10
const util = require('util');
const EventEmitter = require('events');
const http = require('http');
@@ -11,6 +11,7 @@ const is = require('@sindresorhus/is');
const timer = require('@szmarczak/http-timer');
const timedOut = require('./utils/timed-out');
const getBodySize = require('./utils/get-body-size');
+const isFormData = require('./utils/is-form-data').default;
const getResponse = require('./get-response');
const progress = require('./progress');
const {CacheError, UnsupportedProtocolError, MaxRedirectsError, RequestError, TimeoutError} = require('./errors');
@@ -280,8 +281,39 @@ module.exports = (options, input) => {
setImmediate(async () => {
try {
+ for (const hook of options.hooks.beforeRequest) {
+ // eslint-disable-next-line no-await-in-loop
+ await hook(options);
+ }
+
+ // Serialize body
+ const {body, headers} = options;
+ const isForm = !is.nullOrUndefined(options.form);
+ const isJSON = !is.nullOrUndefined(options.json);
+ if (!is.nullOrUndefined(body)) {
+ if (isForm || isJSON) {
+ throw new TypeError('The `body` option cannot be used with the `json` option or `form` option');
+ }
+
+ if (is.object(body) && isFormData(body)) {
+ // Special case for https://github.com/form-data/form-data
+ headers['content-type'] = headers['content-type'] || `multipart/form-data; boundary=${body.getBoundary()}`;
+ } else if (!is.nodeStream(body) && !is.string(body) && !is.buffer(body)) {
+ throw new TypeError('The `body` option must be a stream.Readable, string, Buffer, Object or Array');
+ }
+ } else if (isForm) {
+ if (!is.object(options.form)) {
+ throw new TypeError('The `form` option must be an Object');
+ }
+
+ headers['content-type'] = headers['content-type'] || 'application/x-www-form-urlencoded';
+ options.body = (new URLSearchParams(options.form)).toString();
+ } else if (isJSON) {
+ headers['content-type'] = headers['content-type'] || 'application/json';
+ options.body = JSON.stringify(options.json);
+ }
+
// Convert buffer to stream to receive upload progress events (#322)
- const {body} = options;
if (is.buffer(body)) {
options.body = toReadableStream(body);
uploadBodySize = body.length;
@@ -289,15 +321,14 @@ module.exports = (options, input) => {
uploadBodySize = await getBodySize(options);
}
- if (is.undefined(options.headers['content-length']) && is.undefined(options.headers['transfer-encoding'])) {
+ if (is.undefined(headers['content-length']) && is.undefined(headers['transfer-encoding'])) {
if ((uploadBodySize > 0 || options.method === 'PUT') && !is.null(uploadBodySize)) {
- options.headers['content-length'] = uploadBodySize;
+ headers['content-length'] = uploadBodySize;
}
}
- for (const hook of options.hooks.beforeRequest) {
- // eslint-disable-next-line no-await-in-loop
- await hook(options);
+ if (!options.stream && options.responseType === 'json' && is.undefined(headers.accept)) {
+ options.headers.accept = 'application/json';
}
requestUrl = options.href || (new URL(options.path, urlLib.format(options))).toString();
diff --git a/test/arguments.js b/test/arguments.js
index 2ebfd4034..cfb21061a 100644
--- a/test/arguments.js
+++ b/test/arguments.js
@@ -131,8 +131,8 @@ test('should ignore empty searchParams object', async t => {
t.is((await got(`${s.url}/test`, {searchParams: {}})).requestUrl, `${s.url}/test`);
});
-test('should throw when body is set to object', async t => {
- await t.throwsAsync(got(`${s.url}/`, {body: {}}), TypeError);
+test('should throw on invalid type of body', async t => {
+ await t.throwsAsync(got(`${s.url}/`, {body: false}), TypeError);
});
test('WHATWG URL support', async t => {
@@ -145,11 +145,6 @@ test('should return streams when using stream option', async t => {
t.is(data.toString(), 'ok');
});
-test('should ignore JSON option when using stream option', async t => {
- const data = await pEvent(got(`${s.url}/stream`, {stream: true, json: true}), 'data');
- t.is(data.toString(), 'ok');
-});
-
test('accepts `url` as an option', async t => {
await t.notThrowsAsync(got({url: `${s.url}/test`}));
});
diff --git a/test/create.js b/test/create.js
index 86ce1a1c7..3e18c69f5 100644
--- a/test/create.js
+++ b/test/create.js
@@ -22,8 +22,8 @@ test.after('cleanup', async () => {
});
test('preserve global defaults', async t => {
- const globalHeaders = (await got(s.url, {json: true})).body;
- const instanceHeaders = (await got.extend()(s.url, {json: true})).body;
+ const globalHeaders = await got(s.url).json();
+ const instanceHeaders = await got.extend()(s.url).json();
t.deepEqual(instanceHeaders, globalHeaders);
});
@@ -33,7 +33,7 @@ test('support instance defaults', async t => {
'user-agent': 'custom-ua-string'
}
});
- const headers = (await instance(s.url, {json: true})).body;
+ const headers = await instance(s.url).json();
t.is(headers['user-agent'], 'custom-ua-string');
});
@@ -43,12 +43,11 @@ test('support invocation overrides', async t => {
'user-agent': 'custom-ua-string'
}
});
- const headers = (await instance(s.url, {
- json: true,
+ const headers = await instance(s.url, {
headers: {
'user-agent': 'different-ua-string'
}
- })).body;
+ }).json();
t.is(headers['user-agent'], 'different-ua-string');
});
@@ -63,7 +62,7 @@ test('curry previous instance defaults', async t => {
'x-bar': 'bar'
}
});
- const headers = (await instanceB(s.url, {json: true})).body;
+ const headers = await instanceB(s.url).json();
t.is(headers['x-foo'], 'foo');
t.is(headers['x-bar'], 'bar');
});
@@ -72,9 +71,7 @@ test('custom headers (extend)', async t => {
const options = {headers: {unicorn: 'rainbow'}};
const instance = got.extend(options);
- const headers = (await instance(`${s.url}/`, {
- json: true
- })).body;
+ const headers = await instance(`${s.url}/`).json();
t.is(headers.unicorn, 'rainbow');
});
@@ -108,9 +105,7 @@ test('create', async t => {
return next(options);
}
});
- const headers = (await instance(s.url, {
- json: true
- })).body;
+ const headers = await instance(s.url).json();
t.is(headers.unicorn, 'rainbow');
t.is(headers['user-agent'], undefined);
});
@@ -127,9 +122,7 @@ test('hooks are merged on got.extend()', t => {
test('custom endpoint with custom headers (extend)', async t => {
const instance = got.extend({headers: {unicorn: 'rainbow'}, baseUrl: s.url});
- const headers = (await instance('/', {
- json: true
- })).body;
+ const headers = await instance('/').json();
t.is(headers.unicorn, 'rainbow');
t.not(headers['user-agent'], undefined);
});
diff --git a/test/error.js b/test/error.js
index e8a052048..e74d3267c 100644
--- a/test/error.js
+++ b/test/error.js
@@ -66,30 +66,18 @@ test('dns message', async t => {
t.is(error.method, 'GET');
});
-test('options.body error message', async t => {
- await t.throwsAsync(got(s.url, {body: {}}), {
- message: 'The `body` option must be a stream.Readable, string or Buffer'
- });
-});
-
-test('options.body json error message', async t => {
- await t.throwsAsync(got(s.url, {body: Buffer.from('test'), json: true}), {
- message: 'The `body` option must be an Object or Array when the `json` option is used'
- });
-});
-
test('options.body form error message', async t => {
- await t.throwsAsync(got(s.url, {body: Buffer.from('test'), form: true}), {
- message: 'The `body` option must be an Object when the `form` option is used'
+ await t.throwsAsync(got(s.url, {body: Buffer.from('test'), form: ''}), {
+ message: 'The `body` option cannot be used with the `json` option or `form` option'
});
});
-test('no plain object restriction on body', async t => {
+test('no plain object restriction on json body', async t => {
function CustomObject() {
this.a = 123;
}
- const {body} = await got(`${s.url}/body`, {body: new CustomObject(), json: true});
+ const body = await got(`${s.url}/body`, {json: new CustomObject()}).json();
t.deepEqual(body, {a: 123});
});
@@ -186,7 +174,7 @@ test('catch error in mimicResponse', async t => {
});
test('errors are thrown directly when options.stream is true', t => {
- t.throws(() => got(s.url, {stream: true, body: {}}), {
- message: 'The `body` option must be a stream.Readable, string or Buffer'
+ t.throws(() => got(s.url, {stream: true, hooks: false}), {
+ message: 'Parameter `hooks` must be an object, not boolean'
});
});
diff --git a/test/headers.js b/test/headers.js
index 3b6f82f91..87a829fad 100644
--- a/test/headers.js
+++ b/test/headers.js
@@ -25,22 +25,21 @@ test.after('cleanup', async () => {
});
test('user-agent', async t => {
- const headers = (await got(s.url, {json: true})).body;
+ const headers = await got(s.url).json();
t.is(headers['user-agent'], `${pkg.name}/${pkg.version} (https://github.com/sindresorhus/got)`);
});
test('accept-encoding', async t => {
- const headers = (await got(s.url, {json: true})).body;
+ const headers = await got(s.url).json();
t.is(headers['accept-encoding'], 'gzip, deflate');
});
test('do not override accept-encoding', async t => {
- const headers = (await got(s.url, {
- json: true,
+ const headers = await got(s.url, {
headers: {
'accept-encoding': 'gzip'
}
- })).body;
+ }).json();
t.is(headers['accept-encoding'], 'gzip');
});
@@ -48,7 +47,7 @@ test('do not remove user headers from `url` object argument', async t => {
const headers = (await got({
hostname: s.host,
port: s.port,
- json: true,
+ responseType: 'json',
protocol: 'http:',
headers: {
'X-Request-Id': 'value'
@@ -62,28 +61,26 @@ test('do not remove user headers from `url` object argument', async t => {
});
test('do not set accept-encoding header when decompress options is false', async t => {
- const {body: headers} = await got(s.url, {
- json: true,
+ const headers = await got(s.url, {
decompress: false
- });
+ }).json();
t.false(Reflect.has(headers, 'accept-encoding'));
});
test('accept header with json option', async t => {
- let headers = (await got(s.url, {json: true})).body;
+ let headers = await got(s.url).json();
t.is(headers.accept, 'application/json');
- headers = (await got(s.url, {
+ headers = await got(s.url, {
headers: {
accept: ''
- },
- json: true
- })).body;
+ }
+ }).json();
t.is(headers.accept, '');
});
test('host', async t => {
- const headers = (await got(s.url, {json: true})).body;
+ const headers = await got(s.url).json();
t.is(headers.host, `localhost:${s.port}`);
});
@@ -92,7 +89,7 @@ test('transform names to lowercase', async t => {
headers: {
'ACCEPT-ENCODING': 'identity'
},
- json: true
+ responseType: 'json'
})).body;
t.is(headers['accept-encoding'], 'identity');
});
@@ -197,26 +194,26 @@ test('non-existent headers set to undefined are omitted', async t => {
});
test('preserve port in host header if non-standard port', async t => {
- const {body} = await got(s.url, {json: true});
+ const body = await got(s.url).json();
t.is(body.host, 'localhost:' + s.port);
});
test('strip port in host header if explicit standard port (:80) & protocol (HTTP)', async t => {
- const {body} = await got('http://httpbin.org:80/headers', {json: true});
+ const body = await got('http://httpbin.org:80/headers').json();
t.is(body.headers.Host, 'httpbin.org');
});
test('strip port in host header if explicit standard port (:443) & protocol (HTTPS)', async t => {
- const {body} = await got('https://httpbin.org:443/headers', {json: true});
+ const body = await got('https://httpbin.org:443/headers').json();
t.is(body.headers.Host, 'httpbin.org');
});
test('strip port in host header if implicit standard port & protocol (HTTP)', async t => {
- const {body} = await got('http://httpbin.org/headers', {json: true});
+ const body = await got('http://httpbin.org/headers').json();
t.is(body.headers.Host, 'httpbin.org');
});
test('strip port in host header if implicit standard port & protocol (HTTPS)', async t => {
- const {body} = await got('https://httpbin.org/headers', {json: true});
+ const body = await got('https://httpbin.org/headers').json();
t.is(body.headers.Host, 'httpbin.org');
});
diff --git a/test/hooks.js b/test/hooks.js
index ee4b136fc..40f323373 100644
--- a/test/hooks.js
+++ b/test/hooks.js
@@ -66,7 +66,7 @@ test.after('cleanup', async () => {
test('async hooks', async t => {
const {body} = await got(s.url, {
- json: true,
+ responseType: 'json',
hooks: {
beforeRequest: [
async options => {
@@ -207,7 +207,7 @@ test('init allows modifications', async t => {
test('beforeRequest is called with options', async t => {
await got(s.url, {
- json: true,
+ responseType: 'json',
hooks: {
beforeRequest: [
options => {
@@ -221,7 +221,7 @@ test('beforeRequest is called with options', async t => {
test('beforeRequest allows modifications', async t => {
const {body} = await got(s.url, {
- json: true,
+ responseType: 'json',
hooks: {
beforeRequest: [
options => {
@@ -235,7 +235,7 @@ test('beforeRequest allows modifications', async t => {
test('beforeRedirect is called with options', async t => {
await got(`${s.url}/redirect`, {
- json: true,
+ responseType: 'json',
hooks: {
beforeRedirect: [
options => {
@@ -249,7 +249,7 @@ test('beforeRedirect is called with options', async t => {
test('beforeRedirect allows modifications', async t => {
const {body} = await got(`${s.url}/redirect`, {
- json: true,
+ responseType: 'json',
hooks: {
beforeRedirect: [
options => {
@@ -263,7 +263,7 @@ test('beforeRedirect allows modifications', async t => {
test('beforeRetry is called with options', async t => {
await got(`${s.url}/retry`, {
- json: true,
+ responseType: 'json',
retry: 1,
throwHttpErrors: false,
hooks: {
@@ -280,7 +280,7 @@ test('beforeRetry is called with options', async t => {
test('beforeRetry allows modifications', async t => {
const {body} = await got(`${s.url}/retry`, {
- json: true,
+ responseType: 'json',
hooks: {
beforeRetry: [
options => {
@@ -294,7 +294,7 @@ test('beforeRetry allows modifications', async t => {
test('afterResponse is called with response', async t => {
await got(`${s.url}`, {
- json: true,
+ responseType: 'json',
hooks: {
afterResponse: [
response => {
@@ -309,7 +309,7 @@ test('afterResponse is called with response', async t => {
test('afterResponse allows modifications', async t => {
const {body} = await got(`${s.url}`, {
- json: true,
+ responseType: 'json',
hooks: {
afterResponse: [
response => {
diff --git a/test/http.js b/test/http.js
index 33ce45ebd..8f529e144 100644
--- a/test/http.js
+++ b/test/http.js
@@ -69,7 +69,7 @@ test('doesn\'t throw on throwHttpErrors === false', async t => {
});
test('invalid protocol throws', async t => {
- const error = await t.throwsAsync(got('c:/nope.com', {json: true}));
+ const error = await t.throwsAsync(got('c:/nope.com').json());
t.is(error.constructor, got.UnsupportedProtocolError);
});
diff --git a/test/merge-instances.js b/test/merge-instances.js
index ada1f868a..665bb72e0 100644
--- a/test/merge-instances.js
+++ b/test/merge-instances.js
@@ -24,7 +24,7 @@ test('merging instances', async t => {
const instanceB = got.extend({baseUrl: s.url});
const merged = got.mergeInstances(instanceA, instanceB);
- const headers = (await merged('/', {json: true})).body;
+ const headers = await merged('/').json();
t.is(headers.unicorn, 'rainbow');
t.not(headers['user-agent'], undefined);
});
@@ -55,7 +55,7 @@ test('merges default handlers & custom handlers', async t => {
});
const merged = got.mergeInstances(instanceA, instanceB);
- const {body: headers} = await merged(s.url, {json: true});
+ const headers = await merged(s.url).json();
t.is(headers.unicorn, 'rainbow');
t.is(headers.cat, 'meow');
});
@@ -69,7 +69,7 @@ test('merging one group & one instance', async t => {
const merged = got.mergeInstances(instanceA, instanceB, instanceC);
const doubleMerged = got.mergeInstances(merged, instanceD);
- const headers = (await doubleMerged(s.url, {json: true})).body;
+ const headers = await doubleMerged(s.url).json();
t.is(headers.dog, 'woof');
t.is(headers.cat, 'meow');
t.is(headers.bird, 'tweet');
@@ -87,7 +87,7 @@ test('merging two groups of merged instances', async t => {
const merged = got.mergeInstances(groupA, groupB);
- const headers = (await merged(s.url, {json: true})).body;
+ const headers = await merged(s.url).json();
t.is(headers.dog, 'woof');
t.is(headers.cat, 'meow');
t.is(headers.bird, 'tweet');
diff --git a/test/post.js b/test/post.js
index 7e88c7bd7..9f9e6b80c 100644
--- a/test/post.js
+++ b/test/post.js
@@ -51,31 +51,29 @@ test('sends Streams', async t => {
test('sends plain objects as forms', async t => {
const {body} = await got(s.url, {
- body: {such: 'wow'},
- form: true
+ form: {such: 'wow'}
});
t.is(body, 'such=wow');
});
test('does NOT support sending arrays as forms', async t => {
await t.throwsAsync(got(s.url, {
- body: ['such', 'wow'],
- form: true
+ form: ['such', 'wow']
}), TypeError);
});
test('sends plain objects as JSON', async t => {
const {body} = await got(s.url, {
- body: {such: 'wow'},
- json: true
+ json: {such: 'wow'},
+ responseType: 'json'
});
t.deepEqual(body, {such: 'wow'});
});
test('sends arrays as JSON', async t => {
const {body} = await got(s.url, {
- body: ['such', 'wow'],
- json: true
+ json: ['such', 'wow'],
+ responseType: 'json'
});
t.deepEqual(body, ['such', 'wow']);
});
@@ -132,18 +130,14 @@ test('content-type header is not overriden when object in options.body', async t
headers: {
'content-type': 'doge'
},
- body: {
+ json: {
such: 'wow'
},
- json: true
+ responseType: 'json'
});
t.is(headers['content-type'], 'doge');
});
-test('throws when json body is not a plain object or array', async t => {
- await t.throwsAsync(got(`${s.url}`, {body: '{}', json: true}), TypeError);
-});
-
test('throws when form body is not a plain object or array', async t => {
- await t.throwsAsync(got(`${s.url}`, {body: 'such=wow', form: true}), TypeError);
+ await t.throwsAsync(got(`${s.url}`, {form: 'such=wow'}), TypeError);
});
diff --git a/test/promise.js b/test/promise.js
index e02362ced..fa66b6540 100644
--- a/test/promise.js
+++ b/test/promise.js
@@ -20,13 +20,13 @@ test.after('cleanup', async () => {
});
test('should emit request event as promise', async t => {
- await got(s.url, {json: true}).on('request', request => {
+ await got(s.url).json().on('request', request => {
t.true(request instanceof ClientRequest);
});
});
test('should emit response event as promise', async t => {
- await got(s.url, {json: true}).on('response', response => {
+ await got(s.url).json().on('response', response => {
t.true(response instanceof Transform);
t.true(response.readable);
t.is(response.statusCode, 200);
diff --git a/test/json-parse.js b/test/response-parse.js
similarity index 50%
rename from test/json-parse.js
rename to test/response-parse.js
index 777912a29..1dff3e2d9 100644
--- a/test/json-parse.js
+++ b/test/response-parse.js
@@ -4,11 +4,13 @@ import {createServer} from './helpers/server';
let s;
+const jsonResponse = '{"data":"dog"}';
+
test.before('setup', async () => {
s = await createServer();
s.on('/', (request, response) => {
- response.end('{"data":"dog"}');
+ response.end(jsonResponse);
});
s.on('/invalid', (request, response) => {
@@ -22,7 +24,7 @@ test.before('setup', async () => {
s.on('/non200', (request, response) => {
response.statusCode = 500;
- response.end('{"data":"dog"}');
+ response.end(jsonResponse);
});
s.on('/non200-invalid', (request, response) => {
@@ -41,42 +43,70 @@ test.after('cleanup', async () => {
await s.close();
});
-test('parses response', async t => {
- t.deepEqual((await got(s.url, {json: true})).body, {data: 'dog'});
+test('options.resolveBodyOnly works', async t => {
+ t.deepEqual(await got(s.url, {responseType: 'json', resolveBodyOnly: true}), {data: 'dog'});
+});
+
+test('JSON response', async t => {
+ t.deepEqual((await got(s.url, {responseType: 'json'})).body, {data: 'dog'});
+});
+
+test('Buffer response', async t => {
+ t.deepEqual((await got(s.url, {responseType: 'buffer'})).body, Buffer.from(jsonResponse));
+});
+
+test('Text response', async t => {
+ t.is((await got(s.url, {responseType: 'text'})).body, jsonResponse);
+});
+
+test('JSON response - promise.json()', async t => {
+ t.deepEqual(await got(s.url).json(), {data: 'dog'});
+});
+
+test('Buffer response - promise.buffer()', async t => {
+ t.deepEqual(await got(s.url).buffer(), Buffer.from(jsonResponse));
+});
+
+test('Text response - promise.text()', async t => {
+ t.is(await got(s.url).text(), jsonResponse);
+});
+
+test('throws an error on invalid response type', async t => {
+ await t.throwsAsync(() => got(s.url, {responseType: 'invalid'}), /^Failed to parse body of type 'invalid'/);
});
-test('not parses responses without a body', async t => {
- const {body} = await got(`${s.url}/no-body`, {json: true});
+test('doesn\'t parse responses without a body', async t => {
+ const body = await got(`${s.url}/no-body`).json();
t.is(body, '');
});
test('wraps parsing errors', async t => {
- const error = await t.throwsAsync(got(`${s.url}/invalid`, {json: true}));
+ const error = await t.throwsAsync(got(`${s.url}/invalid`, {responseType: 'json'}));
t.regex(error.message, /Unexpected token/);
t.true(error.message.includes(error.hostname), error.message);
t.is(error.path, '/invalid');
});
test('parses non-200 responses', async t => {
- const error = await t.throwsAsync(got(`${s.url}/non200`, {json: true}));
+ const error = await t.throwsAsync(got(`${s.url}/non200`, {responseType: 'json'}));
t.deepEqual(error.response.body, {data: 'dog'});
});
test('ignores errors on invalid non-200 responses', async t => {
- const error = await t.throwsAsync(got(`${s.url}/non200-invalid`, {json: true}));
+ const error = await t.throwsAsync(got(`${s.url}/non200-invalid`, {responseType: 'json'}));
t.is(error.message, 'Response code 500 (Internal Server Error)');
t.is(error.response.body, 'Internal error');
t.is(error.path, '/non200-invalid');
});
test('should have statusCode in error', async t => {
- const error = await t.throwsAsync(got(`${s.url}/invalid`, {json: true}));
+ const error = await t.throwsAsync(got(`${s.url}/invalid`, {responseType: 'json'}));
t.is(error.constructor, got.ParseError);
t.is(error.statusCode, 200);
});
test('should set correct headers', async t => {
- const {body: headers} = await got(`${s.url}/headers`, {json: true, body: {}});
+ const {body: headers} = await got(`${s.url}/headers`, {responseType: 'json', json: {}});
t.is(headers['content-type'], 'application/json');
t.is(headers.accept, 'application/json');
});
diff --git a/test/stream.js b/test/stream.js
index 34c503332..1fd4e8991 100644
--- a/test/stream.js
+++ b/test/stream.js
@@ -43,8 +43,8 @@ test.after('cleanup', async () => {
await s.close();
});
-test('options.json is ignored', t => {
- t.notThrows(() => got.stream(s.url, {json: true}));
+test('options.responseType is ignored', t => {
+ t.notThrows(() => got.stream(s.url, {responseType: 'json'}));
});
test('returns readable stream', async t => {