Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and use QueryParameterBag #145

Merged
merged 1 commit into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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