Repository: sindresorhus/got
Pull request #663 (2018-11-14 22:33:16)

Add Got options onto responses and errors
It would be helpful to have the options set on `response.req` to identify how the request was made.
For ex, in the `afterResponse` hook, in an event of a failed request, we would like to log the request options.
```javascript
afterResponse: [
      res => {
        if (res.statusCode !== 200) {
          logger.error(
            { res, options: res.req.options, message: res.body },
            'api error'
          );
        }
        return res;
      },
    ],

sindresorhus: It should be `request`, not `req`.
michaltk: @sindresorhus @szmarczak any chance of this getting merged soon?

Patch:
diff --git a/migration-guides.md b/migration-guides.md
new file mode 100644
index 0000000..0aa6b7a
--- /dev/null
+++ b/migration-guides.md
@@ -0,0 +1,171 @@
+# Migration guides
+
+> :star: Switching from other HTTP request libraries to Got :star:
+
+### Migrating from Request
+
+You may think it's too hard to switch, but it's really not. 🦄
+
+Let's take the very first example from Request's readme:
+
+```js
+const request = require('request');
+
+request('https://google.com', (error, response, body) => {
+	console.log('error:', error); // Print the error if one occurred
+	console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
+	console.log('body:', body); // Print the HTML for the Google homepage
+});
+```
+
+With Got, it is:
+
+```js
+const got = require('got');
+
+(async () => {
+	try {
+		const response = await got('https://google.com');
+		console.log('statusCode:', response.statusCode);
+		console.log('body:', response.body);
+	} catch (error) {
+		console.log('error:', error);
+	}
+})();
+```
+
+Looks better now, huh? 😎
+
+#### Common options
+
+Both Request and Got accept [`http.request` options](https://nodejs.org/api/http.html#http_http_request_options_callback).
+
+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)
+
+So if you're familiar with them, you're good to go.
+
+Oh, and one more thing... There's no `time` option. Assume [it's always true](https://github.com/sindresorhus/got#timings).
+
+#### Renamed options
+
+Readability is very important to us, so we have different names for these options:
+
+- `qs` → [`query`](https://github.com/sindresorhus/got#query)
+- `strictSSL` → [`rejectUnauthorized`](https://github.com/sindresorhus/got#rejectUnauthorized)
+- `gzip` → [`decompress`](https://github.com/sindresorhus/got#decompress)
+- `jar` → [`cookieJar`](https://github.com/sindresorhus/got#cookiejar) (accepts [`tough-cookie`](https://github.com/salesforce/tough-cookie) jar)
+
+It's more clear, isn't it?
+
+#### Changes in behavior
+
+The [`timeout` option](https://github.com/sindresorhus/got#timeout) has some extra features. You can [set timeouts on particular events](readme.md#timeout)!
+
+The [`query` option](https://github.com/sindresorhus/got#query) is always serialized using [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) unless it's a `string`.
+
+The [`baseUrl` option](https://github.com/sindresorhus/got#baseurl) appends the ending slash if it's not present.
+
+There's no `maxRedirects` option. It's always set to `10`.
+
+To use streams, just call `got.stream(url, options)` or `got(url, {stream: true, ...}`).
+
+#### Breaking changes
+
+- 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.
+- No `forever` option. You need to use [forever-agent](https://github.com/request/forever-agent).
+- No `proxy` option. You need to [pass a custom agent](readme.md#proxies).
+- No `removeRefererHeader` option. You can remove the referer header in a [`beforeRequest` hook](https://github.com/sindresorhus/got#hooksbeforeRequest):
+
+```js
+const gotInstance = got.extend({
+	hooks: {
+		beforeRequest: [
+			options => {
+				delete options.headers.referer;
+			}
+		]
+	}
+});
+
+gotInstance(url, options);
+```
+
+- No `jsonReviver`/`jsonReplacer` option, but you can use hooks for that too:
+
+```js
+const gotInstance = got.extend({
+	hooks: {
+		beforeRequest: [
+			options => {
+				if (options.json && options.jsonReplacer) {
+					const newBody = {};
+					for (const [key, value] of Object.entries(options.body)) {
+						let newValue = value;
+						do {
+							newValue = options.jsonReplacer(key, newValue);
+						} while (typeof newValue === 'object');
+
+						newBody[key] = newValue;
+					}
+
+					// #1 solution (faster)
+					options.body = newBody;
+
+					// #2 solution (slower)
+					options.body = JSON.parse(JSON.stringify(options.body, options.jsonReplacer));
+				}
+			}
+		],
+		afterResponse: [
+			response => {
+				// TODO in Got: We need to make the `options` public somehow
+				if (options.json && options.jsonReviver) {
+					response.body = JSON.stringify(JSON.parse(response.body, options.jsonReviver));
+				}
+
+				return response;
+			}
+		]
+	}
+});
+
+gotInstance(url, options);
+```
+
+Hooks are powerful, aren't they? [Read more](readme.md#hooks) to see what else you achieve using hooks.
+
+#### More about streams
+
+Let's take a quick look at another example from Request's readme:
+
+```js
+http.createServer((req, res) => {
+	if (req.url === '/doodle.png') {
+		req.pipe(request('https://example.com/doodle.png')).pipe(res);
+	}
+});
+```
+
+The cool feature here is that Request can proxy headers with the stream, but Got can do that too:
+
+```js
+http.createServer((req, res) => {
+	if (req.url === '/doodle.png') {
+		req.pipe(got.stream('https://example.com/doodle.png')).pipe(res);
+	}
+});
+```
+
+Nothing has really changed. Just remember to use `got.stream(url, options)` or `got(url, {stream: true, …`}). That's it!
+
+#### You're good to go!
+
+Well, you have already come this far. Take a look at the [documentation](readme.md#highlights). It's worth the time to read it. There are [some great tips](readme.md#aborting-the-request). If something is unclear or doesn't work as it should, don't hesitate to open an issue.
diff --git a/readme.md b/readme.md
index 255c96b..713ac98 100644
--- a/readme.md
+++ b/readme.md
@@ -42,8 +42,9 @@ Got is for Node.js. For browsers, we recommend [Ky](https://github.com/sindresor
 - [Used by ~2000 packages and ~500K repos](https://github.com/sindresorhus/got/network/dependents)
 - Actively maintained
 
-[See how Got compares to other HTTP libraries](#comparison)
+[Moving from Request?](migration-guides.md)
 
+[See how Got compares to other HTTP libraries](#comparison)
 
 ## Install
 
@@ -451,6 +452,14 @@ const instance = got.extend({
 
 The response object will typically be a [Node.js HTTP response stream](https://nodejs.org/api/http.html#http_class_http_incomingmessage), however, if returned from the cache it will be a [response-like object](https://github.com/lukechilds/responselike) which behaves in the same way.
 
+##### request
+
+Type: `Object`
+
+**Note:** This is not a [http.ClientRequest](https://nodejs.org/api/http.html#http_class_http_clientrequest).
+
+- `gotOptions` - The options that were set on this request.
+
 ##### body
 
 Type: `string` `Object` *(depending on `options.json`)*
@@ -661,7 +670,7 @@ The default Got options.
 
 ## Errors
 
-Each error contains (if available) `body`, `statusCode`, `statusMessage`, `host`, `hostname`, `method`, `path`, `protocol` and `url` properties to make debugging easier.
+Each error contains (if available) `body`, `statusCode`, `statusMessage`, `host`, `hostname`, `method`, `path`, `protocol`, `url`, and `gotOptions` properties to make debugging easier.
 
 In Promise mode, the `response` is attached to the error.
 
diff --git a/source/errors.js b/source/errors.js
index 8030ee2..fcf9548 100644
--- a/source/errors.js
+++ b/source/errors.js
@@ -21,7 +21,8 @@ class GotError extends Error {
 			path: options.path,
 			socketPath: options.socketPath,
 			protocol: options.protocol,
-			url: options.href
+			url: options.href,
+			gotOptions: options
 		});
 	}
 }
diff --git a/source/request-as-event-emitter.js b/source/request-as-event-emitter.js
index d13d793..7104627 100644
--- a/source/request-as-event-emitter.js
+++ b/source/request-as-event-emitter.js
@@ -92,6 +92,9 @@ module.exports = (options, input) => {
 				response.retryCount = retryCount;
 				response.timings = timings;
 				response.redirectUrls = redirects;
+				response.request = {
+					gotOptions: options
+				};
 
 				const rawCookies = response.headers['set-cookie'];
 				if (options.cookieJar && rawCookies) {


Test patch:
diff --git a/test/cache.js b/test/cache.js
index 0c2dc55..979f102 100644
--- a/test/cache.js
+++ b/test/cache.js
@@ -110,6 +110,21 @@ test('Redirects are cached and re-used internally', async t => {
 	t.is(firstResponse.body, secondResponse.body);
 });
 
+test('Cached response should have got options', async t => {
+	const endpoint = '/cache';
+	const cache = new Map();
+	const options = {
+		url: s.url + endpoint,
+		auth: 'foo:bar',
+		cache
+	};
+
+	await got(options);
+	const secondResponse = await got(options);
+
+	t.is(secondResponse.request.gotOptions.auth, options.auth);
+});
+
 test('Cache error throws got.CacheError', async t => {
 	const endpoint = '/no-store';
 	const cache = {};
diff --git a/test/error.js b/test/error.js
index a0bc2ed..cb81312 100644
--- a/test/error.js
+++ b/test/error.js
@@ -112,6 +112,16 @@ test('custom body', async t => {
 	t.is(error.body, 'not');
 });
 
+test('contains Got options', async t => {
+	const options = {
+		url: s.url,
+		auth: 'foo:bar'
+	};
+
+	const error = await t.throwsAsync(got(options));
+	t.is(error.gotOptions.auth, options.auth);
+});
+
 test('no status message is overriden by the default one', async t => {
 	const error = await t.throwsAsync(got(`${s.url}/no-status-message`));
 	t.is(error.statusCode, 400);
diff --git a/test/http.js b/test/http.js
index f0d6482..1b86ae9 100644
--- a/test/http.js
+++ b/test/http.js
@@ -91,3 +91,12 @@ test('requestUrl response when sending url as param', async t => {
 test('response contains url', async t => {
 	t.is((await got(s.url)).url, `${s.url}/`);
 });
+
+test('response contains got options', async t => {
+	const options = {
+		url: s.url,
+		auth: 'foo:bar'
+	};
+
+	t.is((await got(options)).request.gotOptions.auth, options.auth);
+});