Skip to content

Commit

Permalink
feat(util): use node:querystring for buildURL (#1677)
Browse files Browse the repository at this point in the history
* feat(util): use node:querystring for buildURL

* fix: skip test on v12

* fix: handle case for empty object
  • Loading branch information
KhafraDev authored Oct 3, 2022
1 parent b6ae2a8 commit bc636c5
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 124 deletions.
38 changes: 4 additions & 34 deletions lib/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const net = require('net')
const { InvalidArgumentError } = require('./errors')
const { Blob } = require('buffer')
const nodeUtil = require('util')
const { stringify } = require('querystring')

function nop () {}

Expand All @@ -26,46 +27,15 @@ function isBlobLike (object) {
)
}

function isObject (val) {
return val !== null && typeof val === 'object'
}

// this escapes all non-uri friendly characters
function encode (val) {
return encodeURIComponent(val)
}

// based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
function buildURL (url, queryParams) {
if (url.includes('?') || url.includes('#')) {
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
}
if (!isObject(queryParams)) {
throw new Error('Query params must be an object')
}

const parts = []
for (let [key, val] of Object.entries(queryParams)) {
if (val === null || typeof val === 'undefined') {
continue
}

if (!Array.isArray(val)) {
val = [val]
}

for (const v of val) {
if (isObject(v)) {
throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
}
parts.push(encode(key) + '=' + encode(v))
}
}

const serializedParams = parts.join('&')
const stringified = stringify(queryParams)

if (serializedParams) {
url += '?' + serializedParams
if (stringified) {
url += '?' + stringified
}

return url
Expand Down
93 changes: 3 additions & 90 deletions test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ test('basic get with query params', (t) => {
foo: '1',
bar: 'bar',
'%60~%3A%24%2C%2B%5B%5D%40%5E*()-': '%60~%3A%24%2C%2B%5B%5D%40%5E*()-',
multi: ['1', '2']
multi: ['1', '2'],
nullVal: '',
undefinedVal: ''
})

res.statusCode = 200
Expand Down Expand Up @@ -137,95 +139,6 @@ test('basic get with query params', (t) => {
})
})

test('basic get with query params with object throws an error', (t) => {
t.plan(1)

const server = createServer((req, res) => {
t.fail()
})
t.teardown(server.close.bind(server))

const query = {
obj: { id: 1 }
}

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
keepAliveTimeout: 300e3
})
t.teardown(client.close.bind(client))

const signal = new EE()
client.request({
signal,
path: '/',
method: 'GET',
query
}, (err, data) => {
t.equal(err.message, 'Passing object as a query param is not supported, please serialize to string up-front')
})
})
})

test('basic get with non-object query params throws an error', (t) => {
t.plan(1)

const server = createServer((req, res) => {
t.fail()
})
t.teardown(server.close.bind(server))

const query = '{ obj: { id: 1 } }'

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
keepAliveTimeout: 300e3
})
t.teardown(client.close.bind(client))

const signal = new EE()
client.request({
signal,
path: '/',
method: 'GET',
query
}, (err, data) => {
t.equal(err.message, 'Query params must be an object')
})
})
})

test('basic get with query params with date throws an error', (t) => {
t.plan(1)

const date = new Date()
const server = createServer((req, res) => {
t.fail()
})
t.teardown(server.close.bind(server))

const query = {
dateObj: date
}

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
keepAliveTimeout: 300e3
})
t.teardown(client.close.bind(client))

const signal = new EE()
client.request({
signal,
path: '/',
method: 'GET',
query
}, (err, data) => {
t.equal(err.message, 'Passing object as a query param is not supported, please serialize to string up-front')
})
})
})

test('basic get with query params fails if url includes hashmark', (t) => {
t.plan(1)

Expand Down
25 changes: 25 additions & 0 deletions test/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,28 @@ test('parseRawHeaders', (t) => {
t.plan(1)
t.same(util.parseRawHeaders(['key', 'value', Buffer.from('key'), Buffer.from('value')]), ['key', 'value', 'key', 'value'])
})

test('buildURL', { skip: process.version.startsWith('v12.') }, (t) => {
const tests = [
[{ id: BigInt(123456) }, 'id=123456'],
[{ date: new Date() }, 'date='],
[{ obj: { id: 1 } }, 'obj='],
[{ params: ['a', 'b', 'c'] }, 'params=a&params=b&params=c'],
[{ bool: true }, 'bool=true'],
[{ number: 123456 }, 'number=123456'],
[{ string: 'hello' }, 'string=hello'],
[{ null: null }, 'null='],
[{ void: undefined }, 'void='],
[{ fn: function () {} }, 'fn='],
[{}, '']
]

const base = 'https://www.google.com'

for (const [input, output] of tests) {
const expected = `${base}${output ? `?${output}` : output}`
t.equal(util.buildURL(base, input), expected)
}

t.end()
})

0 comments on commit bc636c5

Please sign in to comment.