Skip to content

Commit 429db40

Browse files
committedApr 26, 2020
Fix options.searchParams duplicates
Fixes #1188
1 parent 63c1b72 commit 429db40

File tree

3 files changed

+63
-33
lines changed

3 files changed

+63
-33
lines changed
 

‎source/core/index.ts

+50-32
Original file line numberDiff line numberDiff line change
@@ -560,21 +560,37 @@ export default class Request extends Duplex implements RequestEvents<Request> {
560560

561561
static normalizeArguments(url?: string | URL, options?: Options, defaults?: Defaults): NormalizedOptions {
562562
const rawOptions = options;
563+
const searchParameters = options?.searchParams;
564+
const hooks = options?.hooks;
563565

564566
if (is.object(url) && !is.urlInstance(url)) {
565-
options = {...defaults as NormalizedOptions, ...(url as Options), ...options};
567+
options = {...defaults, ...(url as Options), ...options};
566568
} else {
567569
if (url && options && options.url) {
568570
throw new TypeError('The `url` option is mutually exclusive with the `input` argument');
569571
}
570572

571-
options = {...defaults as NormalizedOptions, ...options};
573+
options = {...defaults, ...options};
572574

573575
if (url) {
574576
options.url = url;
575577
}
576578
}
577579

580+
// Prevent duplicating default search params & hooks
581+
if (searchParameters === undefined) {
582+
delete options.searchParams;
583+
} else {
584+
options.searchParams = searchParameters;
585+
}
586+
587+
if (hooks === undefined) {
588+
delete options.hooks;
589+
} else {
590+
options.hooks = hooks;
591+
}
592+
593+
// Setting options to `undefined` turns off its functionalities
578594
if (rawOptions && defaults) {
579595
for (const key in rawOptions) {
580596
// @ts-ignore Dear TypeScript, all object keys are strings (or symbols which are NOT enumerable).
@@ -637,6 +653,28 @@ export default class Request extends Duplex implements RequestEvents<Request> {
637653
throw new TypeError('Parameter `auth` is deprecated. Use `username` / `password` instead.');
638654
}
639655

656+
// `options.searchParams`
657+
if (options.searchParams) {
658+
if (!is.string(options.searchParams) && !(options.searchParams instanceof URLSearchParams)) {
659+
validateSearchParameters(options.searchParams);
660+
}
661+
662+
options.searchParams = new URLSearchParams(options.searchParams as Record<string, string>);
663+
664+
// `normalizeArguments()` is also used to merge options
665+
if (defaults?.searchParams) {
666+
defaults.searchParams.forEach((value, key) => {
667+
(options!.searchParams as URLSearchParams).append(key, value);
668+
});
669+
}
670+
} else {
671+
options.searchParams = defaults?.searchParams;
672+
}
673+
674+
// `options.username` & `options.password`
675+
options.username = options.username ?? '';
676+
options.password = options.password ?? '';
677+
640678
// `options.prefixUrl` & `options.url`
641679
if (options.prefixUrl) {
642680
options.prefixUrl = options.prefixUrl.toString();
@@ -675,7 +713,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
675713
get: () => prefixUrl
676714
});
677715

678-
// Protocol check
716+
// Support UNIX sockets
679717
let {protocol} = options.url;
680718

681719
if (protocol === 'unix:') {
@@ -684,23 +722,25 @@ export default class Request extends Duplex implements RequestEvents<Request> {
684722
options.url = new URL(`http://unix${options.url.pathname}${options.url.search}`);
685723
}
686724

725+
// Set search params
726+
if (options.searchParams) {
727+
options.url.search = options.searchParams.toString();
728+
}
729+
730+
// Trigger search params normalization
687731
if (options.url.search) {
688732
const triggerSearchParameters = '_GOT_INTERNAL_TRIGGER_NORMALIZATION';
689733

690734
options.url.searchParams.append(triggerSearchParameters, '');
691735
options.url.searchParams.delete(triggerSearchParameters);
692736
}
693737

738+
// Protocol check
694739
if (protocol !== 'http:' && protocol !== 'https:') {
695740
throw new UnsupportedProtocolError(options as NormalizedOptions);
696741
}
697-
}
698742

699-
// `options.username` & `options.password`
700-
options.username = options.username ?? '';
701-
options.password = options.password ?? '';
702-
703-
if (options.url) {
743+
// Update `username` & `password`
704744
options.url.username = options.username;
705745
options.url.password = options.password;
706746
}
@@ -725,27 +765,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
725765
}
726766
}
727767

728-
// `options.searchParams`
729-
if (options.searchParams) {
730-
if (!is.string(options.searchParams) && !(options.searchParams instanceof URLSearchParams)) {
731-
validateSearchParameters(options.searchParams);
732-
}
733-
734-
options.searchParams = new URLSearchParams(options.searchParams as Record<string, string>);
735-
736-
// `normalizeArguments()` is also used to merge options
737-
const defaultsAsOptions = defaults as Options | undefined;
738-
if (defaultsAsOptions && defaultsAsOptions.searchParams instanceof URLSearchParams) {
739-
defaultsAsOptions.searchParams.forEach((value, key) => {
740-
(options!.searchParams as URLSearchParams).append(key, value);
741-
});
742-
}
743-
744-
if (options.url) {
745-
options.url.search = options.searchParams.toString();
746-
}
747-
}
748-
749768
// `options.cache`
750769
const {cache} = options;
751770
if (cache) {
@@ -782,7 +801,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
782801
}
783802

784803
// `options.hooks`
785-
const areHooksUserDefined = options.hooks !== defaults?.hooks;
786804
options.hooks = {...options.hooks};
787805

788806
for (const event of knownHookEvents) {
@@ -798,7 +816,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
798816
}
799817
}
800818

801-
if (defaults && areHooksUserDefined) {
819+
if (defaults) {
802820
for (const event of knownHookEvents) {
803821
const defaultHooks = defaults.hooks[event];
804822

‎test/arguments.ts

+12
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ test('overrides `searchParams` from options', withServer, async (t, server, got)
138138
t.is(body, '/?test=wow');
139139
});
140140

141+
test('does not duplicate `searchParams`', withServer, async (t, server, got) => {
142+
server.get('/', echoUrl);
143+
144+
const instance = got.extend({
145+
searchParams: new URLSearchParams({foo: '123'})
146+
});
147+
148+
const body = await instance('?bar=456').text();
149+
150+
t.is(body, '/?foo=123');
151+
});
152+
141153
test('escapes `searchParams` parameter values', withServer, async (t, server, got) => {
142154
server.get('/', echoUrl);
143155

‎test/error.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ test('`http.request` error through CacheableRequest', async t => {
178178
});
179179
});
180180

181-
test('errors are thrown directly when options.stream is true', t => {
181+
test('errors are thrown directly when options.isStream is true', t => {
182182
t.throws(() => {
183183
// @ts-ignore Error tests
184184
got('https://example.com', {isStream: true, hooks: false});

0 commit comments

Comments
 (0)
Please sign in to comment.