Skip to content
Open
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
17 changes: 17 additions & 0 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,23 @@ export type UrlFor<Routes extends LookupList, Options extends any = URLOptions>
>
): { method: 'DELETE'; url: string; form: { action: string; method: 'DELETE' } }

/**
* Make URL for a QUERY route. An error will be raised if the route doesn't
* exist.
*
* ```ts
* urlFor.query('users.search') // { method: 'QUERY', url: '/users/search' }
* urlFor.query('users.show', [1]) // Error: Route not found QUERY@users.show
* ```
*/
query<RouteIdentifier extends keyof Routes['QUERY'] & string>(
...[identifier, params, options]: RouteBuilderArguments<
RouteIdentifier,
Routes['QUERY'][RouteIdentifier],
Options
>
): { method: 'QUERY'; url: string; form: { action: string; method: 'QUERY' } }

/**
* Make URL for a custom route method. An error will be raised if the route doesn't
* exist for the same method.
Expand Down
17 changes: 17 additions & 0 deletions src/client/url_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,23 @@ export function createUrlBuilder<Routes extends LookupList>(
}
}

urlFor.query = function urlForMethodQuery(...[identifier, params, options]) {
const method = 'QUERY'
const url = createUrlForRoute(identifier, params, options, method)

return {
url,
method,
toString() {
return url
},
form: {
action: url,
method,
},
}
}

urlFor.method = function urlForCustomMethod(method, ...[identifier, params, options]) {
const url = createUrlForRoute(identifier, params, options, method)
return {
Expand Down
13 changes: 13 additions & 0 deletions src/router/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,19 @@ export class Router extends Macroable {
return this.route(pattern, ['DELETE'], handler)
}

/**
* Define `QUERY` route
* @param pattern - The route pattern
* @param handler - Route handler (function, string, or controller tuple)
* @returns The created route instance
*/
query<T extends Constructor<any>>(
pattern: string,
handler: string | RouteFn | [LazyImport<T> | T, GetControllerHandlers<T>?]
) {
return this.route(pattern, ['QUERY'], handler)
}

/**
* Creates a group of routes. A route group can apply transforms
* to routes in bulk
Expand Down
17 changes: 17 additions & 0 deletions src/router/signed_url_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,23 @@ export function createSignedUrlBuilder<Routes extends LookupList>(
}
}

signedRoute.query = function routeQuery(...[identifier, params, options]) {
const method = 'QUERY'
const url = createSignedUrlForRoute(identifier, params, options, method)

return {
url,
method,
toString() {
return url
},
form: {
action: url,
method,
},
}
}

signedRoute.method = function routeGet(method, ...[identifier, params, options]) {
const url = createSignedUrlForRoute(identifier, params, options, method)

Expand Down
20 changes: 20 additions & 0 deletions tests/router/router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ test.group('Router | add', () => {
const putRoute = router.put('/', '#controllers/home.update')
const patchRoute = router.patch('/', '#controllers/home.updatePatch')
const deleteRoute = router.delete('/', '#controllers/home.destroy')
const queryRoute = router.query('/', '#controllers/home.search')
const anyRoute = router.any('/', '#controllers/home.handle')

assert.containSubset(getRoute.toJSON(), {
Expand Down Expand Up @@ -70,6 +71,15 @@ test.group('Router | add', () => {
name: 'home.destroy',
})

assert.containSubset(queryRoute.toJSON(), {
pattern: '/',
methods: ['QUERY'],
meta: {},
matchers: {},
domain: 'root',
name: 'home.search',
})

assert.containSubset(anyRoute.toJSON(), {
pattern: '/',
methods: ['HEAD', 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
Expand Down Expand Up @@ -1126,6 +1136,7 @@ test.group('Router | handler', () => {
async update() {}
async updatePatch() {}
async destroy() {}
async search() {}
async handle() {}
}

Expand Down Expand Up @@ -1155,6 +1166,10 @@ test.group('Router | handler', () => {
assert.isObject(deleteRoute.toJSON().handler)
assert.property(deleteRoute.toJSON().handler, 'handle')

const queryRoute = router.query('/', [HomeController, 'search'])
assert.isObject(queryRoute.toJSON().handler)
assert.property(queryRoute.toJSON().handler, 'handle')

const anyRoute = router.any('/', [HomeController, 'handle'])
assert.isObject(anyRoute.toJSON().handler)
assert.property(anyRoute.toJSON().handler, 'handle')
Expand All @@ -1168,6 +1183,7 @@ test.group('Router | handler', () => {
async update() {}
async updatePatch() {}
async destroy() {}
async search() {}
async handle() {}
}

Expand All @@ -1191,6 +1207,10 @@ test.group('Router | handler', () => {
assert.isObject(deleteRoute.toJSON().handler)
assert.property(deleteRoute.toJSON().handler, 'handle')

const queryRoute = router.query('/', [HomeController, 'search'])
assert.isObject(queryRoute.toJSON().handler)
assert.property(queryRoute.toJSON().handler, 'handle')

const anyRoute = router.any('/', [HomeController, 'handle'])
assert.isObject(anyRoute.toJSON().handler)
assert.property(anyRoute.toJSON().handler, 'handle')
Expand Down
28 changes: 27 additions & 1 deletion tests/router/url_builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,20 @@ test.group('URLBuilder', () => {
paramsTuple: [string]
}
}
QUERY: {
'users.show': {
params: { id: string }
paramsTuple: [string]
}
}
}>()
.merge({ router })
.create()

router.route('/users', ['POST'], () => {}).as('users.index')
router.route('/users/:id', ['GET', 'PUT', 'PATCH', 'DELETE'], () => {}).as('users.show')
router
.route('/users/:id', ['GET', 'PUT', 'PATCH', 'DELETE', 'QUERY'], () => {})
.as('users.show')
router.commit()

assert.containSubset(urlFor.get('users.show', { id: '1' }), { method: 'GET', url: '/users/1' })
Expand Down Expand Up @@ -158,6 +166,16 @@ test.group('URLBuilder', () => {
})
assert.equal(`${urlFor.delete('users.show', { id: '1' })}`, '/users/1')

assert.containSubset(urlFor.query('users.show', { id: '1' }), {
method: 'QUERY',
url: '/users/1',
})
assert.deepEqual(urlFor.query('users.show', { id: '1' }).form, {
method: 'QUERY',
action: '/users/1',
})
assert.equal(`${urlFor.query('users.show', { id: '1' })}`, '/users/1')

assert.containSubset(urlFor.method('GET', 'users.show', { id: '1' }), {
method: 'GET',
url: '/users/1',
Expand Down Expand Up @@ -251,6 +269,12 @@ test.group('URLBuilder', () => {
paramsTuple: [string]
}
}
QUERY: {
'users.search': {
params?: {}
paramsTuple: [string]
}
}
}>()
.merge({ router, encryption })
.create()
Expand All @@ -259,6 +283,7 @@ test.group('URLBuilder', () => {
router.route('/users/:id', ['GET'], () => {}).as('users.show')
router.route('/users/:id', ['PUT', 'PATCH'], () => {}).as('users.update')
router.route('/users/:id', ['DELETE'], () => {}).as('users.delete')
router.route('/users/search', ['QUERY'], () => {}).as('users.search')
router.commit()

function verifySignature(uri: string) {
Expand All @@ -276,6 +301,7 @@ test.group('URLBuilder', () => {
verifySignature(`${signedUrlFor.put('users.update', { id: '1' })}`)
verifySignature(`${signedUrlFor.patch('users.update', { id: '1' })}`)
verifySignature(`${signedUrlFor.delete('users.delete', { id: '1' })}`)
verifySignature(`${signedUrlFor.query('users.search')}`)
verifySignature(`${signedUrlFor.method('GET', 'users.show', { id: '1' })}`)
})

Expand Down
21 changes: 21 additions & 0 deletions tests/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { AppFactory } from '@adonisjs/application/factories'
import { createServer, IncomingMessage, ServerResponse } from 'node:http'

import { Router } from '../src/router/main.ts'
import { httpServer as httpServerFactory } from '../factories/http_server.ts'
import { HttpContext } from '../src/http_context/main.ts'
import { ServerFactory } from '../factories/server_factory.ts'
import { defineNamedMiddleware } from '../src/define_middleware.ts'
Expand Down Expand Up @@ -101,6 +102,26 @@ test.group('Server | Response handling', () => {
assert.equal(text, 'handled')
})

test('invoke router handler for QUERY method requests', async ({ assert }) => {
const app = new AppFactory().create(BASE_URL, () => {})
const server = new ServerFactory().merge({ app }).create()

await app.init()

server.use([])
server.getRouter().query('/users/search', async ({ request }) => request.method())
await server.boot()

const { url } = await httpServerFactory.create(server.handle.bind(server))
const response = await fetch(`${url}/users/search`, {
method: 'QUERY',
body: 'name = "virk"',
})

assert.equal(response.status, 200)
assert.equal(await response.text(), 'QUERY')
})

test('use route handler return value when response.send is not called', async ({ assert }) => {
const app = new AppFactory().create(BASE_URL, () => {})
const server = new ServerFactory().merge({ app }).create()
Expand Down