Skip to content
This repository was archived by the owner on Nov 15, 2024. It is now read-only.

Commit c80612f

Browse files
authored
fix: Serialize array params for GET requests (#285)
1 parent d8fef7d commit c80612f

File tree

6 files changed

+171
-10
lines changed

6 files changed

+171
-10
lines changed

docs/classes/Seam.md

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/interfaces/SeamClientOptions.md

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/modules.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/params-serializer.ts

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

src/seam-connect/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "../types/globals"
1515
import { version } from "../../package.json"
1616
import { ClientSessionsResponse } from "../types"
17+
import { paramsSerializer } from "../lib/params-serializer"
1718

1819
export interface SeamClientOptions {
1920
/* Seam API Key */
@@ -96,6 +97,7 @@ export class Seam extends Routes {
9697
withCredentials: clientSessionToken ? true : false,
9798
...axiosOptions,
9899
baseURL: endpoint,
100+
paramsSerializer,
99101
headers,
100102
})
101103

tests/params-serializer.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import test from "ava"
2+
3+
import {
4+
paramsSerializer,
5+
UnserializableParamError,
6+
} from "../src/lib/params-serializer"
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+
t.is(paramsSerializer({ foo: 1, bar: ["", "", ""] }), "bar=&bar=&bar=&foo=1")
56+
t.is(
57+
paramsSerializer({ foo: 1, bar: ["", "a", "2"] }),
58+
"bar=&bar=a&bar=2&foo=1"
59+
)
60+
t.is(
61+
paramsSerializer({ foo: 1, bar: ["", "a", ""] }),
62+
"bar=&bar=a&bar=&foo=1"
63+
)
64+
})
65+
66+
test("cannot serialize single element array params with empty string", (t) => {
67+
t.throws(() => paramsSerializer({ foo: [""] }), {
68+
instanceOf: UnserializableParamError,
69+
})
70+
})
71+
72+
test("cannot serialize unserializable values", (t) => {
73+
t.throws(() => paramsSerializer({ foo: {} }), {
74+
instanceOf: UnserializableParamError,
75+
})
76+
t.throws(() => paramsSerializer({ foo: { x: 2 } }), {
77+
instanceOf: UnserializableParamError,
78+
})
79+
t.throws(() => paramsSerializer({ foo: () => {} }), {
80+
instanceOf: UnserializableParamError,
81+
})
82+
})
83+
84+
test("cannot serialize array params with unserializable values", (t) => {
85+
t.throws(() => paramsSerializer({ bar: ["a", null] }), {
86+
instanceOf: UnserializableParamError,
87+
})
88+
t.throws(() => paramsSerializer({ bar: ["a", undefined] }), {
89+
instanceOf: UnserializableParamError,
90+
})
91+
t.throws(() => paramsSerializer({ bar: ["a", ["s"]] }), {
92+
instanceOf: UnserializableParamError,
93+
})
94+
t.throws(() => paramsSerializer({ bar: ["a", []] }), {
95+
instanceOf: UnserializableParamError,
96+
})
97+
t.throws(() => paramsSerializer({ bar: ["a", {}] }), {
98+
instanceOf: UnserializableParamError,
99+
})
100+
t.throws(() => paramsSerializer({ bar: ["a", { x: 2 }] }), {
101+
instanceOf: UnserializableParamError,
102+
})
103+
t.throws(() => paramsSerializer({ bar: ["a", () => {}] }), {
104+
instanceOf: UnserializableParamError,
105+
})
106+
})

0 commit comments

Comments
 (0)