Skip to content

Commit 9291b8a

Browse files
authored
Add paramsSerializer
1 parent 0e67fb5 commit 9291b8a

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

src/lib/params-serializer.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import test from 'ava'
2+
3+
import {
4+
paramsSerializer,
5+
UnserializeableParamError,
6+
} from './params-serializer.js'
7+
8+
test('serializes empty object', (t) => {
9+
t.is(paramsSerializer({}), '')
10+
})
11+
12+
test('serializes string', (t) => {
13+
t.is(paramsSerializer({ foo: 'd' }), 'foo=d')
14+
t.is(paramsSerializer({ foo: 'null' }), 'foo=null')
15+
t.is(paramsSerializer({ foo: 'undefined' }), 'foo=undefined')
16+
t.is(paramsSerializer({ foo: '0' }), 'foo=0')
17+
})
18+
19+
test('serializes number', (t) => {
20+
t.is(paramsSerializer({ foo: 1 }), 'foo=1')
21+
t.is(paramsSerializer({ foo: 23.8 }), 'foo=23.8')
22+
})
23+
24+
test('serializes boolean', (t) => {
25+
t.is(paramsSerializer({ foo: true }), 'foo=true')
26+
t.is(paramsSerializer({ foo: false }), 'foo=false')
27+
})
28+
29+
test('removes undefined params', (t) => {
30+
t.is(paramsSerializer({ bar: undefined }), '')
31+
t.is(paramsSerializer({ foo: 1, bar: undefined }), 'foo=1')
32+
})
33+
34+
test('removes null params', (t) => {
35+
t.is(paramsSerializer({ bar: null }), '')
36+
t.is(paramsSerializer({ foo: 1, bar: null }), 'foo=1')
37+
})
38+
39+
test('serializes empty array params', (t) => {
40+
t.is(paramsSerializer({ bar: [] }), 'bar=')
41+
t.is(paramsSerializer({ foo: 1, bar: [] }), 'bar=&foo=1')
42+
})
43+
44+
test('serializes array params with one value', (t) => {
45+
t.is(paramsSerializer({ bar: ['a'] }), 'bar=a')
46+
t.is(paramsSerializer({ foo: 1, bar: ['a'] }), 'bar=a&foo=1')
47+
})
48+
49+
test('serializes array params with many values', (t) => {
50+
t.is(paramsSerializer({ foo: 1, bar: ['a', '2'] }), 'bar=a&bar=2&foo=1')
51+
t.is(
52+
paramsSerializer({ foo: 1, bar: ['null', '2', 'undefined'] }),
53+
'bar=null&bar=2&bar=undefined&foo=1',
54+
)
55+
})
56+
57+
test('cannot serialize unserializeable values', (t) => {
58+
t.throws(() => paramsSerializer({ foo: {} }), {
59+
instanceOf: UnserializeableParamError,
60+
})
61+
t.throws(() => paramsSerializer({ foo: { x: 2 } }), {
62+
instanceOf: UnserializeableParamError,
63+
})
64+
t.throws(() => paramsSerializer({ foo: () => {} }), {
65+
instanceOf: UnserializeableParamError,
66+
})
67+
})
68+
69+
test('cannot serialize array params with unserializeable values', (t) => {
70+
t.throws(() => paramsSerializer({ bar: ['a', null] }), {
71+
instanceOf: UnserializeableParamError,
72+
})
73+
t.throws(() => paramsSerializer({ bar: ['a', undefined] }), {
74+
instanceOf: UnserializeableParamError,
75+
})
76+
t.throws(() => paramsSerializer({ bar: ['a', ['s']] }), {
77+
instanceOf: UnserializeableParamError,
78+
})
79+
t.throws(() => paramsSerializer({ bar: ['a', []] }), {
80+
instanceOf: UnserializeableParamError,
81+
})
82+
t.throws(() => paramsSerializer({ bar: ['a', {}] }), {
83+
instanceOf: UnserializeableParamError,
84+
})
85+
t.throws(() => paramsSerializer({ bar: ['a', { x: 2 }] }), {
86+
instanceOf: UnserializeableParamError,
87+
})
88+
t.throws(() => paramsSerializer({ bar: ['a', () => {}] }), {
89+
instanceOf: UnserializeableParamError,
90+
})
91+
})

src/lib/params-serializer.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { CustomParamsSerializer } from 'axios'
2+
3+
export const paramsSerializer: CustomParamsSerializer = (params) => {
4+
const searchParams = new URLSearchParams()
5+
6+
for (const [name, value] of Object.entries(params)) {
7+
if (value == null) continue
8+
9+
if (Array.isArray(value)) {
10+
if (value.length === 0) searchParams.set(name, '')
11+
for (const v of value) {
12+
throwIfUnserializeable(name, v)
13+
searchParams.append(name, v)
14+
}
15+
continue
16+
}
17+
18+
throwIfUnserializeable(name, value)
19+
searchParams.set(name, value)
20+
}
21+
22+
searchParams.sort()
23+
return searchParams.toString()
24+
}
25+
26+
const throwIfUnserializeable = (k, v): void => {
27+
if (v == null) {
28+
throw new UnserializeableParamError(
29+
`Parameter ${k} is ${v} or contains ${v}`,
30+
)
31+
}
32+
33+
if (typeof v === 'function') {
34+
throw new UnserializeableParamError(
35+
`Parameter ${k} is a function or contains a function`,
36+
)
37+
}
38+
39+
if (typeof v === 'object') {
40+
throw new UnserializeableParamError(
41+
`Parameter ${k} is an object or contains an object`,
42+
)
43+
}
44+
}
45+
46+
export class UnserializeableParamError extends Error {
47+
constructor(message: string) {
48+
super(`Could not serialize parameter: ${message}`)
49+
this.name = this.constructor.name
50+
Error.captureStackTrace(this, this.constructor)
51+
}
52+
}

src/lib/seam/connect/axios.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import axios, { type Axios } from 'axios'
22

3+
import { paramsSerializer } from 'lib/params-serializer.js'
4+
35
import { getAuthHeaders } from './auth.js'
46
import {
57
isSeamHttpOptionsWithClient,
@@ -15,7 +17,7 @@ export const createAxiosClient = (
1517
return axios.create({
1618
baseURL: options.endpoint,
1719
withCredentials: isSeamHttpOptionsWithClientSessionToken(options),
18-
paramsSerializer: (params) => new URLSearchParams(params).toString(),
20+
paramsSerializer,
1921
...options.axiosOptions,
2022
headers: {
2123
...getAuthHeaders(options),

0 commit comments

Comments
 (0)