Skip to content

Commit

Permalink
Don't infer POST automatically when specifying body (#756)
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak authored and sindresorhus committed Mar 25, 2019
1 parent 0a79ccb commit e367bdb
Show file tree
Hide file tree
Showing 14 changed files with 55 additions and 55 deletions.
1 change: 1 addition & 0 deletions advanced-creation.md
Expand Up @@ -64,6 +64,7 @@ const jsonGot = got.create(settings);
```js
const defaults = {
options: {
method: 'GET',
retry: {
retries: 2,
methods: [
Expand Down
2 changes: 1 addition & 1 deletion migration-guides.md
Expand Up @@ -105,7 +105,7 @@ const gotInstance = got.extend({
hooks: {
init: [
options => {
if (options.jsonReplacer) {
if (options.jsonReplacer && options.body) {
options.body = JSON.stringify(options.body, options.jsonReplacer);
}
}
Expand Down
15 changes: 8 additions & 7 deletions readme.md
Expand Up @@ -94,7 +94,7 @@ fs.createReadStream('index.html').pipe(got.stream.post('https://sindresorhus.com

### API

It's a `GET` request by default, but can be changed by using different methods or in the `options`.
It's a `GET` request by default, but can be changed by using different methods or via `options.method`.

#### got(url, [options])

Expand Down Expand Up @@ -164,7 +164,7 @@ Type: `string` `Buffer` `stream.Readable` [`form-data` instance](https://github.

**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`.
If present in `options` and `options.method` is not set, it will throw a `TypeError`.

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`.

Expand Down Expand Up @@ -438,7 +438,7 @@ Called with [normalized](source/normalize-arguments.js) [request options](#optio
```js
const got = require('got');

got('https://example.com', {
got.post('https://example.com', {
hooks: {
beforeRetry: [
(options, error, retryCount) => {
Expand Down Expand Up @@ -694,16 +694,17 @@ client.get('/demo');
'x-foo': 'bar'
}
});
const {headers} = (await client.get('/headers').json()).body;
const {headers} = await client.get('/headers').json();
//=> headers['x-foo'] === 'bar'

const jsonClient = client.extend({
responseType: 'json',
resolveBodyOnly: true,
headers: {
'x-baz': 'qux'
}
});
const {headers: headers2} = (await jsonClient.get('/headers')).body;
const {headers: headers2} = await jsonClient.get('/headers');
//=> headers2['x-foo'] === 'bar'
//=> headers2['x-baz'] === 'qux'
})();
Expand Down Expand Up @@ -1045,7 +1046,7 @@ By default, if you pass an object to the `body` option it will be stringified us
const got = require('got');

(async () => {
const response = await got('https://httpbin.org/anything', {
const response = await got.post('https://httpbin.org/anything', {
body: {
hello: 'world'
},
Expand All @@ -1063,7 +1064,7 @@ To receive a JSON body you can either set `responseType` option to `json` or use
const got = require('got');

(async () => {
const {body} = await got('https://httpbin.org/anything', {
const {body} = await got.post('https://httpbin.org/anything', {
body: {
hello: 'world'
}
Expand Down
1 change: 1 addition & 0 deletions source/index.ts
Expand Up @@ -3,6 +3,7 @@ import create from './create';

const defaults = {
options: {
method: 'GET',
retry: {
retries: 2,
methods: [
Expand Down
13 changes: 8 additions & 5 deletions source/request-as-event-emitter.ts
Expand Up @@ -19,6 +19,8 @@ import urlToOptions from './utils/url-to-options';
const getMethodRedirectCodes = new Set([300, 301, 302, 303, 304, 305, 307, 308]);
const allMethodRedirectCodes = new Set([300, 303, 307, 308]);

const withoutBody = new Set(['GET', 'HEAD']);

export default (options, input?: TransformStream) => {
const emitter = new EventEmitter();
const redirects = [];
Expand Down Expand Up @@ -292,7 +294,12 @@ export default (options, input?: TransformStream) => {
const {body, headers} = options;
const isForm = !is.nullOrUndefined(options.form);
const isJSON = !is.nullOrUndefined(options.json);
if (!is.nullOrUndefined(body)) {
const isBody = !is.nullOrUndefined(body);
if ((isBody || isForm || isJSON) && withoutBody.has(options.method)) {
throw new TypeError(`The \`${options.method}\` method cannot be used with a body`);
}

if (isBody) {
if (isForm || isJSON) {
throw new TypeError('The `body` option cannot be used with the `json` option or `form` option');
}
Expand All @@ -315,10 +322,6 @@ export default (options, input?: TransformStream) => {
options.body = JSON.stringify(options.json);
}

if (!options.method) {
options.method = is.nullOrUndefined(options.body) ? 'GET' : 'POST';
}

// Convert buffer to stream to receive upload progress events (#322)
if (is.buffer(body)) {
options.body = toReadableStream(body);
Expand Down
4 changes: 2 additions & 2 deletions test/cancel.ts
Expand Up @@ -71,7 +71,7 @@ test('cancel in-progress request', async t => {
body.push('1');

// @ts-ignore
const p = got(helper.url, {body});
const p = got.post(helper.url, {body});

// Wait for the connection to be established before canceling
helper.on('connection', () => {
Expand All @@ -93,7 +93,7 @@ test('cancel in-progress request with timeout', async t => {
body.push('1');

// @ts-ignore
const p = got(helper.url, {body, timeout: 10000});
const p = got.post(helper.url, {body, timeout: 10000});

// Wait for the connection to be established before canceling
helper.on('connection', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/error.ts
Expand Up @@ -68,7 +68,7 @@ test('dns message', async t => {
});

test('options.body form error message', async t => {
await t.throwsAsync(got(s.url, {body: Buffer.from('test'), form: ''}), {
await t.throwsAsync(got.post(s.url, {body: Buffer.from('test'), form: ''}), {
message: 'The `body` option cannot be used with the `json` option or `form` option'
});
});
Expand All @@ -78,7 +78,7 @@ test('no plain object restriction on json body', async t => {
this.a = 123;
}

const body = await got(`${s.url}/body`, {json: new CustomObject()}).json();
const body = await got.post(`${s.url}/body`, {json: new CustomObject()}).json();

t.deepEqual(body, {a: 123});
});
Expand Down
12 changes: 6 additions & 6 deletions test/headers.ts
Expand Up @@ -96,7 +96,7 @@ test('transform names to lowercase', async t => {
});

test('setting content-length to 0', async t => {
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
headers: {
'content-length': 0
},
Expand All @@ -117,7 +117,7 @@ test('sets content-length to 0 when requesting PUT with empty body', async t =>
test('form-data manual content-type', async t => {
const form = new FormData();
form.append('a', 'b');
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
headers: {
'content-type': 'custom'
},
Expand All @@ -130,7 +130,7 @@ test('form-data manual content-type', async t => {
test('form-data automatic content-type', async t => {
const form = new FormData();
form.append('a', 'b');
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
body: form
});
const headers = JSON.parse(body);
Expand All @@ -140,15 +140,15 @@ test('form-data automatic content-type', async t => {
test('form-data sets content-length', async t => {
const form = new FormData();
form.append('a', 'b');
const {body} = await got(s.url, {body: form});
const {body} = await got.post(s.url, {body: form});
const headers = JSON.parse(body);
t.is(headers['content-length'], '157');
});

test('stream as options.body sets content-length', async t => {
const fixture = path.join(__dirname, 'fixtures/stream-content-length');
const {size} = await promisify(fs.stat)(fixture);
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
body: fs.createReadStream(fixture)
});
const headers = JSON.parse(body);
Expand All @@ -157,7 +157,7 @@ test('stream as options.body sets content-length', async t => {

test('buffer as options.body sets content-length', async t => {
const buffer = Buffer.from('unicorn');
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
body: buffer
});
const headers = JSON.parse(body);
Expand Down
3 changes: 2 additions & 1 deletion test/hooks.ts
Expand Up @@ -179,7 +179,7 @@ test('catches beforeError errors', async t => {
});

test('init is called with options', async t => {
await got(s.url, {
await got.post(s.url, {
json: true,
hooks: {
init: [
Expand All @@ -197,6 +197,7 @@ test('init allows modifications', async t => {
hooks: {
init: [
options => {
options.method = 'POST';
options.body = 'foobar';
}
]
Expand Down
41 changes: 17 additions & 24 deletions test/post.ts
Expand Up @@ -28,82 +28,80 @@ test.after('cleanup', async () => {
await s.close();
});

test('GET can have body', async t => {
const {body, headers} = await got.get(s.url, {body: 'hi'});
t.is(body, 'hi');
t.is(headers.method, 'GET');
test('GET cannot have body', async t => {
await t.throwsAsync(got.get(s.url, {body: 'hi'}), 'The `GET` method cannot be used with a body');
});

test('sends strings', async t => {
const {body} = await got(s.url, {body: 'wow'});
const {body} = await got.post(s.url, {body: 'wow'});
t.is(body, 'wow');
});

test('sends Buffers', async t => {
const {body} = await got(s.url, {body: Buffer.from('wow')});
const {body} = await got.post(s.url, {body: Buffer.from('wow')});
t.is(body, 'wow');
});

test('sends Streams', async t => {
const {body} = await got(s.url, {body: toReadableStream('wow')});
const {body} = await got.post(s.url, {body: toReadableStream('wow')});
t.is(body, 'wow');
});

test('sends plain objects as forms', async t => {
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
form: {such: 'wow'}
});
t.is(body, 'such=wow');
});

test('does NOT support sending arrays as forms', async t => {
await t.throwsAsync(got(s.url, {
await t.throwsAsync(got.post(s.url, {
form: ['such', 'wow']
}), TypeError);
});

test('sends plain objects as JSON', async t => {
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
json: {such: 'wow'},
responseType: 'json'
});
t.deepEqual(body, {such: 'wow'});
});

test('sends arrays as JSON', async t => {
const {body} = await got(s.url, {
const {body} = await got.post(s.url, {
json: ['such', 'wow'],
responseType: 'json'
});
t.deepEqual(body, ['such', 'wow']);
});

test('works with empty post response', async t => {
const {body} = await got(`${s.url}/empty`, {body: 'wow'});
const {body} = await got.post(`${s.url}/empty`, {body: 'wow'});
t.is(body, '');
});

test('content-length header with string body', async t => {
const {body} = await got(`${s.url}/headers`, {body: 'wow'});
const {body} = await got.post(`${s.url}/headers`, {body: 'wow'});
const headers = JSON.parse(body);
t.is(headers['content-length'], '3');
});

test('content-length header with Buffer body', async t => {
const {body} = await got(`${s.url}/headers`, {body: Buffer.from('wow')});
const {body} = await got.post(`${s.url}/headers`, {body: Buffer.from('wow')});
const headers = JSON.parse(body);
t.is(headers['content-length'], '3');
});

test('content-length header with Stream body', async t => {
const {body} = await got(`${s.url}/headers`, {body: toReadableStream('wow')});
const {body} = await got.post(`${s.url}/headers`, {body: toReadableStream('wow')});
const headers = JSON.parse(body);
t.is(headers['transfer-encoding'], 'chunked', 'likely failed to get headers at all');
t.is(headers['content-length'], undefined);
});

test('content-length header is not overriden', async t => {
const {body} = await got(`${s.url}/headers`, {
const {body} = await got.post(`${s.url}/headers`, {
body: 'wow',
headers: {
'content-length': '10'
Expand All @@ -114,7 +112,7 @@ test('content-length header is not overriden', async t => {
});

test('content-length header disabled for chunked transfer-encoding', async t => {
const {body} = await got(`${s.url}/headers`, {
const {body} = await got.post(`${s.url}/headers`, {
body: '3\r\nwow\r\n0\r\n',
headers: {
'transfer-encoding': 'chunked'
Expand All @@ -126,7 +124,7 @@ test('content-length header disabled for chunked transfer-encoding', async t =>
});

test('content-type header is not overriden when object in options.body', async t => {
const {body: headers} = await got(`${s.url}/headers`, {
const {body: headers} = await got.post(`${s.url}/headers`, {
headers: {
'content-type': 'doge'
},
Expand All @@ -139,10 +137,5 @@ test('content-type header is not overriden when object in options.body', async t
});

test('throws when form body is not a plain object or array', async t => {
await t.throwsAsync(got(`${s.url}`, {form: 'such=wow'}), TypeError);
});

test('setting JSON will default to POST', async t => {
const {headers} = await got(s.url, {json: {foo: 'bar'}});
t.is(headers.method, 'POST');
await t.throwsAsync(got.post(`${s.url}`, {form: 'such=wow'}), TypeError);
});
4 changes: 2 additions & 2 deletions test/redirects.ts
Expand Up @@ -169,7 +169,7 @@ test('hostname+path in options are not breaking redirects', async t => {
});

test('redirect only GET and HEAD requests', async t => {
const error = await t.throwsAsync(got(`${http.url}/relative`, {body: 'wow'}));
const error = await t.throwsAsync(got.post(`${http.url}/relative`, {body: 'wow'}));
t.is(error.message, 'Response code 302 (Found)');
// @ts-ignore
t.is(error.path, '/relative');
Expand All @@ -178,7 +178,7 @@ test('redirect only GET and HEAD requests', async t => {
});

test('redirect on 303 response even with post, put, delete', async t => {
const {url, body} = await got(`${http.url}/seeOther`, {body: 'wow'});
const {url, body} = await got.post(`${http.url}/seeOther`, {body: 'wow'});
t.is(url, `${http.url}/`);
t.is(body, 'reached');
});
Expand Down
2 changes: 1 addition & 1 deletion test/response-parse.ts
Expand Up @@ -112,7 +112,7 @@ test('should have statusCode in error', async t => {
});

test('should set correct headers', async t => {
const {body: headers} = await got(`${s.url}/headers`, {responseType: 'json', json: {}});
const {body: headers} = await got.post(`${s.url}/headers`, {responseType: 'json', json: {}});
t.is(headers['content-type'], 'application/json');
t.is(headers.accept, 'application/json');
});

0 comments on commit e367bdb

Please sign in to comment.