Skip to content

Commit

Permalink
add QueryParameterBag
Browse files Browse the repository at this point in the history
  • Loading branch information
marcuspoehls committed Oct 28, 2023
1 parent eac3e8d commit 5ee1166
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 4 deletions.
21 changes: 21 additions & 0 deletions packages/contracts/src/http/query-parameter-bag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

import { ParameterBag } from '../index.js'

export interface QueryParameterBag<T> extends ParameterBag<T> {
/**
* Returns the querystring created from all items in this query parameter bag,
* without the leading question mark `?`.
*
* **Notice:** the returned querystring is encoded. Node.js automatically
* encodes the querystring to ensure a valid URL. Some characters would
* break the URL string otherwise. This way ensures the valid string.
*/
toQuerystring (): string

/**
* Returns the decoded querystring by running the result of `toQuerystring`
* through `decodeURIComponent`. This method is useful to debug during
* development. It’s recommended to use `toQuerystring` in production.
*/
toQuerystringDecoded (): string
}
3 changes: 2 additions & 1 deletion packages/contracts/src/http/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IncomingHttpHeaders, IncomingMessage } from 'node:http'
import { InteractsWithState } from './concerns/interacts-with-state.js'
import { RequestCookieBuilderCallback } from './cookie-options-builder.js'
import { InteractsWithContentTypes } from './concerns/interacts-with-content-types.js'
import { QueryParameterBag } from './query-parameter-bag.js'

export interface HttpRequestCtor extends MacroableCtor {
/**
Expand Down Expand Up @@ -66,7 +67,7 @@ export interface HttpRequest extends InteractsWithState, InteractsWithContentTyp
/**
* Returns the query parameter bag.
*/
query(): ParameterBag<string | string[]>
query(): QueryParameterBag<string | string[]>

/**
* Returns the plain query string, without the leading ?.
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export { StateBag, HttpStateData } from './http/concerns/state-bag.js'
export { FileBag } from './http/file-bag.js'
export { InputBag } from './http/input-bag.js'
export { ParameterBag } from './http/parameter-bag.js'
export { QueryParameterBag } from './http/query-parameter-bag.js'
export { HttpKernel } from './http/kernel.js'
export { HttpMethods } from './http/methods.js'
export { Middleware, MiddlewareCtor, InlineMiddlewareHandler } from './http/middleware.js'
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/server/input-bag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class InputBag<T> implements InputBagContract<T> {
/**
* Create a new instance.
*/
constructor (attributes: Dict<T>) {
constructor (attributes: Dict<T> | undefined) {
this.attributes = attributes ?? {}
}

Expand Down
31 changes: 31 additions & 0 deletions packages/http/src/server/query-parameter-bag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

import { ParameterBag } from './parameter-bag.js'
import { QueryParameterBag as QueryParameterBagContract } from '@supercharge/contracts'

export class QueryParameterBag<T> extends ParameterBag<T> implements QueryParameterBagContract<T> {
/**
* Returns the query string created from all items in this query parameter bag,
* without the leading question mark `?`.
*
* **Notice:** the returned querystring is encoded. Node.js automatically
* encodes the querystring to ensure a valid URL. Some characters would
* break the URL string otherwise. This way ensures the valid string.
*/
toQuerystring (): string {
return new URLSearchParams(
this.all() as Record<string, string>
).toString()
}

/**
* Returns the decoded querystring by running the result of `toQuerystring`
* through `decodeURIComponent`. This method is useful to debug during
* development. It’s recommended to use `toQuerystring` in production.
*/
toQuerystringDecoded (): string {
return decodeURIComponent(
this.toQuerystring()
)
}
}
5 changes: 3 additions & 2 deletions packages/http/src/server/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CookieBag } from './cookie-bag.js'
import { ParameterBag } from './parameter-bag.js'
import { Macroable } from '@supercharge/macroable'
import { RequestHeaderBag } from './request-header-bag.js'
import { QueryParameterBag } from './query-parameter-bag.js'
import { InteractsWithState } from './interacts-with-state.js'
import { IncomingHttpHeaders, IncomingMessage } from 'node:http'
import { CookieOptions, HttpContext, HttpMethods, HttpRequest, InteractsWithContentTypes, Protocol, RequestCookieBuilderCallback } from '@supercharge/contracts'
Expand Down Expand Up @@ -88,8 +89,8 @@ export class Request extends Many(Macroable, InteractsWithState) implements Http
/**
* Returns the request’s query parameters.
*/
query (): ParameterBag<string | string[]> {
return new ParameterBag<string | string[]>(this.koaCtx.query)
query (): QueryParameterBag<string | string[]> {
return new QueryParameterBag(this.koaCtx.query)
}

/**
Expand Down
58 changes: 58 additions & 0 deletions packages/http/test/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,60 @@ test('request.query() returns the querystring', async () => {
.expect(200, { name: 'Supercharge', marcus: 'isCool' })
})

test('request.query().toQuerystring() returns the querystring', async () => {
const server = app
.make(Server)
.use(({ request, response }) => {
if (request.query().has('addFoo')) {
request.query().set('foo', 'bar')
}
return response.payload(
request.query().toQuerystring()
)
})

await Supertest(server.callback())
.get('/')
.expect(200, '')

await Supertest(server.callback())
.get('/?name=Supercharge&marcus=isCool')
.expect(200, 'name=Supercharge&marcus=isCool')

await Supertest(server.callback())
.get('/?addFoo=1&name=Supercharge')
.expect(200, 'addFoo=1&name=Supercharge&foo=bar')
})

test('request.query().toQuerystringDecoded()', async () => {
const server = app
.make(Server)
.use(({ request, response }) => {
if (request.query().has('addFoo')) {
request.query().set('foo', 'bar')
}
return response.payload(
request.query().toQuerystringDecoded()
)
})

await Supertest(server.callback())
.get('/')
.expect(200, '')

await Supertest(server.callback())
.get('/?name=Supercharge&marcus=isCool')
.expect(200, 'name=Supercharge&marcus=isCool')

await Supertest(server.callback())
.get('/?name=Supercharge&marcus[]=isCool&marcus[]=isQuery')
.expect(200, 'name=Supercharge&marcus[]=isCool,isQuery')

await Supertest(server.callback())
.get('/?addFoo=1&name=Supercharge')
.expect(200, 'addFoo=1&name=Supercharge&foo=bar')
})

test('request.all() returns merged query params, payload, and files', async () => {
const server = app
.make(Server)
Expand Down Expand Up @@ -651,6 +705,10 @@ test('querystring', async () => {
await Supertest(server.callback())
.get('/?name=Supercharge')
.expect(200, { querystring: 'name=Supercharge' })

await Supertest(server.callback())
.get('/?name=Supercharge&foo=bar')
.expect(200, { querystring: 'name=Supercharge&foo=bar' })
})

test('fullUrl', async () => {
Expand Down

0 comments on commit 5ee1166

Please sign in to comment.