Skip to content

Commit bc636c5

Browse files
authored
feat(util): use node:querystring for buildURL (nodejs#1677)
* feat(util): use node:querystring for buildURL * fix: skip test on v12 * fix: handle case for empty object
1 parent b6ae2a8 commit bc636c5

File tree

3 files changed

+32
-124
lines changed

3 files changed

+32
-124
lines changed

lib/core/util.js

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const net = require('net')
88
const { InvalidArgumentError } = require('./errors')
99
const { Blob } = require('buffer')
1010
const nodeUtil = require('util')
11+
const { stringify } = require('querystring')
1112

1213
function nop () {}
1314

@@ -26,46 +27,15 @@ function isBlobLike (object) {
2627
)
2728
}
2829

29-
function isObject (val) {
30-
return val !== null && typeof val === 'object'
31-
}
32-
33-
// this escapes all non-uri friendly characters
34-
function encode (val) {
35-
return encodeURIComponent(val)
36-
}
37-
38-
// based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
3930
function buildURL (url, queryParams) {
4031
if (url.includes('?') || url.includes('#')) {
4132
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
4233
}
43-
if (!isObject(queryParams)) {
44-
throw new Error('Query params must be an object')
45-
}
46-
47-
const parts = []
48-
for (let [key, val] of Object.entries(queryParams)) {
49-
if (val === null || typeof val === 'undefined') {
50-
continue
51-
}
52-
53-
if (!Array.isArray(val)) {
54-
val = [val]
55-
}
56-
57-
for (const v of val) {
58-
if (isObject(v)) {
59-
throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
60-
}
61-
parts.push(encode(key) + '=' + encode(v))
62-
}
63-
}
6434

65-
const serializedParams = parts.join('&')
35+
const stringified = stringify(queryParams)
6636

67-
if (serializedParams) {
68-
url += '?' + serializedParams
37+
if (stringified) {
38+
url += '?' + stringified
6939
}
7040

7141
return url

test/client.js

Lines changed: 3 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ test('basic get with query params', (t) => {
9898
foo: '1',
9999
bar: 'bar',
100100
'%60~%3A%24%2C%2B%5B%5D%40%5E*()-': '%60~%3A%24%2C%2B%5B%5D%40%5E*()-',
101-
multi: ['1', '2']
101+
multi: ['1', '2'],
102+
nullVal: '',
103+
undefinedVal: ''
102104
})
103105

104106
res.statusCode = 200
@@ -137,95 +139,6 @@ test('basic get with query params', (t) => {
137139
})
138140
})
139141

140-
test('basic get with query params with object throws an error', (t) => {
141-
t.plan(1)
142-
143-
const server = createServer((req, res) => {
144-
t.fail()
145-
})
146-
t.teardown(server.close.bind(server))
147-
148-
const query = {
149-
obj: { id: 1 }
150-
}
151-
152-
server.listen(0, () => {
153-
const client = new Client(`http://localhost:${server.address().port}`, {
154-
keepAliveTimeout: 300e3
155-
})
156-
t.teardown(client.close.bind(client))
157-
158-
const signal = new EE()
159-
client.request({
160-
signal,
161-
path: '/',
162-
method: 'GET',
163-
query
164-
}, (err, data) => {
165-
t.equal(err.message, 'Passing object as a query param is not supported, please serialize to string up-front')
166-
})
167-
})
168-
})
169-
170-
test('basic get with non-object query params throws an error', (t) => {
171-
t.plan(1)
172-
173-
const server = createServer((req, res) => {
174-
t.fail()
175-
})
176-
t.teardown(server.close.bind(server))
177-
178-
const query = '{ obj: { id: 1 } }'
179-
180-
server.listen(0, () => {
181-
const client = new Client(`http://localhost:${server.address().port}`, {
182-
keepAliveTimeout: 300e3
183-
})
184-
t.teardown(client.close.bind(client))
185-
186-
const signal = new EE()
187-
client.request({
188-
signal,
189-
path: '/',
190-
method: 'GET',
191-
query
192-
}, (err, data) => {
193-
t.equal(err.message, 'Query params must be an object')
194-
})
195-
})
196-
})
197-
198-
test('basic get with query params with date throws an error', (t) => {
199-
t.plan(1)
200-
201-
const date = new Date()
202-
const server = createServer((req, res) => {
203-
t.fail()
204-
})
205-
t.teardown(server.close.bind(server))
206-
207-
const query = {
208-
dateObj: date
209-
}
210-
211-
server.listen(0, () => {
212-
const client = new Client(`http://localhost:${server.address().port}`, {
213-
keepAliveTimeout: 300e3
214-
})
215-
t.teardown(client.close.bind(client))
216-
217-
const signal = new EE()
218-
client.request({
219-
signal,
220-
path: '/',
221-
method: 'GET',
222-
query
223-
}, (err, data) => {
224-
t.equal(err.message, 'Passing object as a query param is not supported, please serialize to string up-front')
225-
})
226-
})
227-
})
228-
229142
test('basic get with query params fails if url includes hashmark', (t) => {
230143
t.plan(1)
231144

test/util.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,28 @@ test('parseRawHeaders', (t) => {
9595
t.plan(1)
9696
t.same(util.parseRawHeaders(['key', 'value', Buffer.from('key'), Buffer.from('value')]), ['key', 'value', 'key', 'value'])
9797
})
98+
99+
test('buildURL', { skip: process.version.startsWith('v12.') }, (t) => {
100+
const tests = [
101+
[{ id: BigInt(123456) }, 'id=123456'],
102+
[{ date: new Date() }, 'date='],
103+
[{ obj: { id: 1 } }, 'obj='],
104+
[{ params: ['a', 'b', 'c'] }, 'params=a&params=b&params=c'],
105+
[{ bool: true }, 'bool=true'],
106+
[{ number: 123456 }, 'number=123456'],
107+
[{ string: 'hello' }, 'string=hello'],
108+
[{ null: null }, 'null='],
109+
[{ void: undefined }, 'void='],
110+
[{ fn: function () {} }, 'fn='],
111+
[{}, '']
112+
]
113+
114+
const base = 'https://www.google.com'
115+
116+
for (const [input, output] of tests) {
117+
const expected = `${base}${output ? `?${output}` : output}`
118+
t.equal(util.buildURL(base, input), expected)
119+
}
120+
121+
t.end()
122+
})

0 commit comments

Comments
 (0)