Skip to content

Commit 617558e

Browse files
committed
fix(query): exclude null-ish values from query encoding
1 parent 7b4e3d9 commit 617558e

File tree

4 files changed

+142
-4
lines changed

4 files changed

+142
-4
lines changed

src/fetch/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,18 @@ export const edenFetch =
7171
} catch (error) {}
7272

7373
const fetch = config?.fetcher || globalThis.fetch
74-
const queryStr = query
75-
? `?${new URLSearchParams(query).toString()}`
74+
75+
const nonNullishQuery = query
76+
? Object.fromEntries(
77+
Object.entries(query).filter(
78+
([_, val]) => val !== undefined && val !== null
79+
)
80+
)
81+
: null
82+
const queryStr = nonNullishQuery
83+
? `?${new URLSearchParams(nonNullishQuery).toString()}`
7684
: ''
85+
7786
const requestUrl = `${server}${endpoint}${queryStr}`
7887
const headers = body
7988
? {

src/treaty2/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,17 @@ const createProxy = (
196196
continue
197197
}
198198

199-
if (typeof value === 'object') {
200-
append(key, JSON.stringify(value))
199+
// Explicitly exclude null and undefined values from url encoding
200+
// to prevent parsing string "null" / string "undefined"
201+
if (value === undefined || value === null) {
201202
continue
202203
}
203204

204205

206+
if (typeof value === 'object') {
207+
append(key, JSON.stringify(value))
208+
continue
209+
}
205210
append(key, `${value}`)
206211
}
207212
}

test/fetch.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,32 @@ const app = new Elysia()
6868
})
6969
}
7070
)
71+
.get(
72+
'/with-query-undefined',
73+
({ query }) => {
74+
return {
75+
query
76+
}
77+
},
78+
{
79+
query: t.Object({
80+
q: t.Undefined(t.String())
81+
})
82+
}
83+
)
84+
.get(
85+
'/with-query-nullish',
86+
({ query }) => {
87+
return {
88+
query
89+
}
90+
},
91+
{
92+
query: t.Object({
93+
q: t.Nullable(t.String())
94+
})
95+
}
96+
)
7197
.listen(8081)
7298

7399
const fetch = edenFetch<typeof app>('http://localhost:8081')
@@ -170,4 +196,27 @@ describe('Eden Fetch', () => {
170196
})
171197
expect(data?.query.q).toBe('A')
172198
})
199+
200+
it('send undefined query', async () => {
201+
const { data, error } = await fetch('/with-query-undefined', {
202+
query: {
203+
q: undefined
204+
}
205+
})
206+
expect(data?.query.q).toBeUndefined()
207+
expect(error).toBeNull()
208+
})
209+
210+
// t.Nullable is impossible to represent with query params
211+
// without elysia specifically parsing 'null'
212+
it('send null query', async () => {
213+
const { data, error } = await fetch('/with-query-nullish', {
214+
query: {
215+
q: null
216+
}
217+
})
218+
expect(data?.query.q).toBeUndefined()
219+
expect(error?.status).toBe(422)
220+
expect(error?.value.type).toBe("validation")
221+
})
173222
})

test/treaty2.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,34 @@ const app = new Elysia()
6464
username: t.String()
6565
})
6666
})
67+
.get('/query-optional', ({ query }) => query, {
68+
query: t.Object({
69+
username: t.Optional(t.String()),
70+
})
71+
})
72+
.get('/query-nullable', ({ query }) => query, {
73+
query: t.Object({
74+
username: t.Nullable(t.String()),
75+
})
76+
})
6777
.get('/queries', ({ query }) => query, {
6878
query: t.Object({
6979
username: t.String(),
7080
alias: t.Literal('Kristen')
7181
})
7282
})
83+
.get('/queries-optional', ({ query }) => query, {
84+
query: t.Object({
85+
username: t.Optional(t.String()),
86+
alias: t.Literal('Kristen')
87+
})
88+
})
89+
.get('/queries-nullable', ({ query }) => query, {
90+
query: t.Object({
91+
username: t.Nullable(t.Number()),
92+
alias: t.Literal('Kristen')
93+
})
94+
})
7395
.post('/queries', ({ query }) => query, {
7496
query: t.Object({
7597
username: t.String(),
@@ -257,6 +279,32 @@ describe('Treaty2', () => {
257279
expect(data).toEqual(query)
258280
})
259281

282+
// t.Nullable is impossible to represent with query params
283+
// without elysia specifically parsing 'null'
284+
it('get null query', async () => {
285+
const query = { username: null }
286+
287+
const { data, error } = await client['query-nullable'].get({
288+
query
289+
})
290+
291+
expect(data).toBeNull()
292+
expect(error?.status).toBe(422)
293+
expect(error?.value.type).toBe("validation")
294+
})
295+
296+
it('get optional query', async () => {
297+
const query = { username: undefined }
298+
299+
const { data } = await client['query-optional'].get({
300+
query
301+
})
302+
303+
expect(data).toEqual({
304+
username: undefined
305+
})
306+
})
307+
260308
it('get queries', async () => {
261309
const query = { username: 'A', alias: 'Kristen' } as const
262310

@@ -267,6 +315,33 @@ describe('Treaty2', () => {
267315
expect(data).toEqual(query)
268316
})
269317

318+
it('get optional queries', async () => {
319+
const query = { username: undefined, alias: 'Kristen' } as const
320+
321+
const { data } = await client['queries-optional'].get({
322+
query
323+
})
324+
325+
expect(data).toEqual({
326+
username: undefined,
327+
alias: 'Kristen'
328+
})
329+
})
330+
331+
// t.Nullable is impossible to represent with query params
332+
// without elysia specifically parsing 'null'
333+
it('get nullable queries', async () => {
334+
const query = { username: null, alias: 'Kristen' } as const
335+
336+
const { data, error } = await client['queries-nullable'].get({
337+
query
338+
})
339+
340+
expect(data).toBeNull()
341+
expect(error?.status).toBe(422)
342+
expect(error?.value.type).toBe("validation")
343+
})
344+
270345
it('post queries', async () => {
271346
const query = { username: 'A', alias: 'Kristen' } as const
272347

0 commit comments

Comments
 (0)