Skip to content

Commit

Permalink
conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
kwhitley committed Jan 7, 2024
2 parents d1772b9 + f01628c commit cf7efa7
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 112 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Changelog

- **v4.0.24**
- fixed: createResponse(undefined) should return undefined (not a Response)
- **v4.1.0**
- added: `HasContent<ContentType>` type to `withContent` (credit [@alexrosenfeld10](https://github.com/alexrosenfeld10))
- added: Adds basic text/formData support to supplement the native JSON support of `withContent`
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ router
```

### 3. It's all Promises.
We `await` every handler, looking for a return value. If we get one, we break the flow and return your value. If we don't, we continue processing handlers/routes until we do. This means that every handler can either be synchronous or async - it's all the same.
itty `await`s every handler, looking for a return value. If it gets one, it breaks the flow and returns the value. If it doesn't, it continues processing handlers/routes until it does. This means that every handler can either be synchronous or async - it's all the same.

When paired with the fact that we can simply return raw data and transform it later, this is AWESOME for working with async APIs, database layers, etc. We don't need to transform anything at the route, we can simply return the Promise (to data) itself!

Expand All @@ -186,7 +186,7 @@ router
```

### 4. Only one required argument. The rest is up to you.
We only require one argument in itty - a Request-like object with the following shape: `{ url, method }` (usually a native [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)). Because itty is not opinionated about [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) creation, there is not "response" argument built in. Every other argument you pass to `route.handle` is given to each handler, in the same order.
itty only requires one argument - a Request-like object with the following shape: `{ url, method }` (usually a native [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)). Because itty is not opinionated about [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) creation, there is not "response" argument built in. Every other argument you pass to `route.handle` is given to each handler, in the same order.

> ### This makes itty one of the most platform-agnostic routers, *period*, as it's able to match up to any platform's signature.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "itty-router",
"version": "4.1.0-next.4",
"version": "4.0.26",
"description": "A tiny, zero-dependency router, designed to make beautiful APIs in any environment.",
"main": "./index.js",
"module": "./index.mjs",
Expand Down
138 changes: 75 additions & 63 deletions src/Router.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'isomorphic-fetch'
import { describe, expect, it, vi } from 'vitest'
import { buildRequest, createTestRunner, extract } from '../test'
import { createTestRunner, extract, toReq } from '../test'
import { Router } from './Router'

const ERROR_MESSAGE = 'Error Message'
Expand All @@ -17,7 +17,7 @@ describe('Router', () => {
{ path: '/foo', callback: vi.fn(extract), method: 'post' },
{
path: '/passthrough',
callback: vi.fn(({ path, name }) => ({ path, name })),
callback: vi.fn(({ method, name }) => ({ method, name })),
method: 'get',
},
]
Expand Down Expand Up @@ -57,18 +57,18 @@ describe('Router', () => {

const router = Router({
routes: [
['GET', /^\/test\.(?<x>[^/]+)\/*$/, [basicHandler]],
['GET', /^\/custom-(?<custom>\d{2,4})$/, [customHandler]],
['GET', /^\/test\.(?<x>[^/]+)\/*$/, [basicHandler], '/test'],
['GET', /^\/custom-(?<custom>\d{2,4})$/, [customHandler], '/custom'],
],
})

await router.handle(buildRequest({ path: '/test.a.b' }))
await router.handle(toReq('/test.a.b'))
expect(basicHandler).toHaveReturnedWith({ x: 'a.b' })

await router.handle(buildRequest({ path: '/custom-12345' }))
await router.handle(toReq('/custom-12345'))
expect(customHandler).not.toHaveBeenCalled() // custom route mismatch

await router.handle(buildRequest({ path: '/custom-123' }))
await router.handle(toReq('/custom-123'))
expect(customHandler).toHaveReturnedWith({ custom: '123' }) // custom route hit
})

Expand All @@ -78,9 +78,9 @@ describe('Router', () => {
const router = Router()

// allows manual loading (after config)
router.routes.push(['GET', /^\/custom2-(?<custom>\w\d{3})$/, [handler]])
router.routes.push(['GET', /^\/custom2-(?<custom>\w\d{3})$/, [handler], '/custom'])

await router.handle(buildRequest({ path: '/custom2-a456' }))
await router.handle(toReq('/custom2-a456'))
expect(handler).toHaveReturnedWith({ custom: 'a456' }) // custom route hit
})

Expand All @@ -98,7 +98,7 @@ describe('Router', () => {
const handler3 = vi.fn((req) => ({ c: 3, ...req }))
r.get('/multi/:id', handler1, handler2, handler3)

await r.handle(buildRequest({ path: '/multi/foo' }))
await r.handle(toReq('/multi/foo'))

expect(handler2).toHaveBeenCalled()
expect(handler3).not.toHaveBeenCalled()
Expand All @@ -110,15 +110,15 @@ describe('Router', () => {
const syncRouter = Router()
syncRouter.get('/foo', () => 3)

const response = syncRouter.handle(buildRequest({ path: '/foo' }))
const response = syncRouter.handle(toReq('/foo'))

expect(typeof response?.then).toBe('function')
expect(typeof response?.catch).toBe('function')
})

it('returns { path, query } from match', async () => {
const route = routes.find((r) => r.path === '/foo/:id')
await router.handle(buildRequest({ path: '/foo/13?foo=bar&cat=dog' }))
await router.handle(toReq('/foo/13?foo=bar&cat=dog'))

expect(route?.callback).toHaveReturnedWith({
params: { id: '13' },
Expand All @@ -128,7 +128,7 @@ describe('Router', () => {

it('BUG: avoids toString prototype bug', async () => {
const route = routes.find((r) => r.path === '/foo/:id')
await router.handle(buildRequest({ path: '/foo/13?toString=value' }))
await router.handle(toReq('/foo/13?toString=value'))

expect(route?.callback).toHaveReturnedWith({
params: { id: '13' },
Expand All @@ -139,9 +139,9 @@ describe('Router', () => {
it('requires exact route match', async () => {
const route = routes.find((r) => r.path === '/')

await router.handle(buildRequest({ path: '/foo' }))
await router.handle(toReq('/foo'))

expect(route.callback).not.toHaveBeenCalled()
expect(route?.callback).not.toHaveBeenCalled()
})

it('returns { method, route } from matched route', async () => {
Expand All @@ -152,10 +152,10 @@ describe('Router', () => {
const router = Router()
router.get(route1, handler).post(route2, handler)

await router.handle(buildRequest({ path: route1, method: 'GET' }))
await router.handle(toReq(route1))
expect(handler).toHaveReturnedWith({ method: 'GET', route: route1 })

await router.handle(buildRequest({ path: route2, method: 'POST' }))
await router.handle(toReq(`POST ${route2}`))
expect(handler).toHaveReturnedWith({ method: 'POST', route: route2 })
})

Expand All @@ -166,30 +166,28 @@ describe('Router', () => {
router.get('/foo/static', handler1)
router.get('/foo/:id', handler2)

await router.handle(buildRequest({ path: '/foo/static' }))
await router.handle(toReq('/foo/static'))
expect(handler1).toHaveBeenCalled()
expect(handler2).not.toHaveBeenCalled()

await router.handle(buildRequest({ path: '/foo/3' }))
await router.handle(toReq('/foo/3'))
expect(handler1).toHaveBeenCalledTimes(1)
expect(handler2).toHaveBeenCalled()
})

it('honors correct method (e.g. GET, POST, etc)', async () => {
const route = routes.find((r) => r.path === '/foo' && r.method === 'post')
await router.handle(buildRequest({ method: 'POST', path: '/foo' }))
await router.handle(toReq('POST /foo'))

expect(route.callback).toHaveBeenCalled()
expect(route!.callback).toHaveBeenCalled()
})

it('passes the entire original request through to the handler', async () => {
const route = routes.find((r) => r.path === '/passthrough')
await router.handle(
buildRequest({ path: '/passthrough', name: 'miffles' })
)
await router.handle({ ...toReq('/passthrough'), name: 'miffles' })

expect(route.callback).toHaveReturnedWith({
path: '/passthrough',
expect(route!.callback).toHaveReturnedWith({
method: 'GET',
name: 'miffles',
})
})
Expand All @@ -204,10 +202,10 @@ describe('Router', () => {
router2.get('/foo', matchHandler)
router1.all('/nested/*', router2.handle).all('*', missingHandler)

await router1.handle(buildRequest({ path: '/foo' }))
await router1.handle(toReq('/foo'))
expect(missingHandler).toHaveBeenCalled()

await router1.handle(buildRequest({ path: '/nested/foo' }))
await router1.handle(toReq('/nested/foo'))
expect(matchHandler).toHaveBeenCalled()
})

Expand All @@ -229,7 +227,7 @@ describe('Router', () => {
r.get('/middleware/*', middleware)
r.get('/middleware/:id', handler)

await r.handle(buildRequest({ path: '/middleware/foo' }))
await r.handle(toReq('/middleware/foo'))

expect(handler).toHaveBeenCalled()
expect(handler).toHaveReturnedWith(13)
Expand All @@ -240,10 +238,10 @@ describe('Router', () => {
const handler = vi.fn()
router.get('/foo/:id?', handler)

await router.handle(buildRequest({ path: '/api/foo' }))
await router.handle(toReq('/api/foo'))
expect(handler).toHaveBeenCalled()

await router.handle(buildRequest({ path: '/api/foo/13' }))
await router.handle(toReq('/api/foo/13'))
expect(handler).toHaveBeenCalledTimes(2)
})

Expand All @@ -252,7 +250,7 @@ describe('Router', () => {
const handler = vi.fn()
router.get('/foo/:id?', handler)

await router.handle(buildRequest({ path: '/foo' }))
await router.handle(toReq('/foo'))
expect(handler).toHaveBeenCalled()
})

Expand All @@ -261,41 +259,20 @@ describe('Router', () => {
const handler = vi.fn((req) => req.params)
router.get('/:id', handler)

await router.handle(buildRequest({ path: '/todos/13' }))
await router.handle(toReq('/todos/13'))
expect(handler).toHaveBeenCalled()
expect(handler).toHaveReturnedWith({ collection: 'todos', id: '13' })
})

it('can handle nested routers', async () => {
const router1 = Router()
const router2 = Router({ base: '/nested' })
const handler1 = vi.fn()
const handler2 = vi.fn()
const handler3 = vi.fn()
router1.get('/pet', handler1)
router1.get('/nested/*', router2.handle)
router2.get('/', handler3)
router2.get('/bar/:id?', handler2)

await router1.handle(buildRequest({ path: '/pet' }))
expect(handler1).toHaveBeenCalled()

await router1.handle(buildRequest({ path: '/nested/bar' }))
expect(handler2).toHaveBeenCalled()

await router1.handle(buildRequest({ path: '/nested' }))
expect(handler3).toHaveBeenCalled()
})

it('allows any method to match an "all" route', async () => {
const router = Router()
const handler = vi.fn()
router.all('/crud/*', handler)

await router.handle(buildRequest({ path: '/crud/foo' }))
await router.handle(toReq('/crud/foo'))
expect(handler).toHaveBeenCalled()

await router.handle(buildRequest({ method: 'POST', path: '/crud/bar' }))
await router.handle(toReq('POST /crud/bar'))
expect(handler).toHaveBeenCalledTimes(2)
})

Expand All @@ -310,7 +287,7 @@ describe('Router', () => {

const escape = (err) => err

await router.handle(buildRequest({ path: '/foo' })).catch(escape)
await router.handle(toReq('/foo')).catch(escape)

expect(handler1).toHaveBeenCalled()
expect(handler2).toHaveBeenCalled()
Expand All @@ -326,7 +303,7 @@ describe('Router', () => {

router.get('/foo', handlerWithError)

await router.handle(buildRequest({ path: '/foo' })).catch(errorHandler)
await router.handle(toReq('/foo')).catch(errorHandler)

expect(handlerWithError).toHaveBeenCalled()
expect(errorHandler).toHaveBeenCalled()
Expand Down Expand Up @@ -376,7 +353,7 @@ describe('Router', () => {
it('can easily create a ThrowableRouter', async () => {
const error = (status, message) => new Response(message, { status })

const ThrowableRouter = (options) =>
const ThrowableRouter = (options = {}) =>
new Proxy(Router(options), {
get:
(obj, prop) =>
Expand All @@ -395,7 +372,7 @@ describe('Router', () => {

router.get('/foo', handlerWithError)

const response = await router.handle(buildRequest({ path: '/foo' }))
const response = await router.handle(toReq('/foo'))

expect(response instanceof Response).toBe(true)
expect(response.status).toBe(500)
Expand All @@ -421,7 +398,7 @@ describe('Router', () => {
const originalA = 'A'
const originalB = {}
r.get('*', h)
const req = buildRequest({ path: '/foo' })
const req: any = toReq('/foo')

await r.handle(req, originalA, originalB)

Expand All @@ -440,7 +417,7 @@ describe('Router', () => {

router.get('/foo', withProxy, handler)

await router.handle(buildRequest({ path: '/foo' }))
await router.handle(toReq('/foo'))

expect(handler).toHaveReturnedWith(proxy)
})
Expand Down Expand Up @@ -507,6 +484,41 @@ describe('Router', () => {
})
})

describe('NESTING', () => {
it('can handle legacy nested routers (with explicit base path)', async () => {
const router1 = Router()
const router2 = Router({ base: '/nested' })
const handler1 = vi.fn()
const handler2 = vi.fn()
const handler3 = vi.fn()
router1.get('/pet', handler1)
router1.get('/nested/*', router2.handle)
router2.get('/', handler3)
router2.get('/bar/:id?', handler2)

await router1.handle(toReq('/pet'))
expect(handler1).toHaveBeenCalled()

await router1.handle(toReq('/nested/bar'))
expect(handler2).toHaveBeenCalled()

await router1.handle(toReq('/nested'))
expect(handler3).toHaveBeenCalled()
})

it('can nest with route params on the nested route if given router.handle and base path', async () => {
const child = Router({ base: '/child/:bar' }).get('/', () => 'child')
const parent = Router()
.get('/', () => 'parent')
.all('/child/:bar/*', child.handle)

console.log({ child: child.routes, parent: parent.routes })

expect(await parent.handle(toReq('/'))).toBe('parent')
expect(await parent.handle(toReq('/child/kitten'))).toBe('child')
})
})

describe('MIDDLEWARE', () => {
it('calls any handler until a return', async () => {
const router = Router()
Expand All @@ -516,7 +528,7 @@ describe('MIDDLEWARE', () => {

router.get('*', h1, h2, h3)

const results = await router.handle(buildRequest({ path: '/' }))
const results = await router.handle(toReq('/'))
expect(h1).toHaveBeenCalled()
expect(h2).toHaveBeenCalled()
expect(h3).toHaveBeenCalled()
Expand Down
Loading

0 comments on commit cf7efa7

Please sign in to comment.