Skip to content

Commit

Permalink
feat(middleware/combine): Introduce combine middleware (#2941)
Browse files Browse the repository at this point in the history
* feat(middleware/predicate): Introduce predicate middleware

* fix: apply `bun run format:fix`

* refactor: rename middleware predicate -> combine
  • Loading branch information
usualoma committed Jul 13, 2024
1 parent e6d253d commit 9a6e52d
Show file tree
Hide file tree
Showing 4 changed files with 421 additions and 0 deletions.
1 change: 1 addition & 0 deletions jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"./pretty-json": "./src/middleware/pretty-json/index.ts",
"./request-id": "./src/middleware/request-id/request-id.ts",
"./secure-headers": "./src/middleware/secure-headers/secure-headers.ts",
"./combine": "./src/middleware/combine/index.ts",
"./ssg": "./src/helper/ssg/index.ts",
"./streaming": "./src/helper/streaming/index.ts",
"./validator": "./src/validator/index.ts",
Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@
"import": "./dist/middleware/secure-headers/index.js",
"require": "./dist/cjs/middleware/secure-headers/index.js"
},
"./combine": {
"types": "./dist/types/middleware/combine/index.d.ts",
"import": "./dist/middleware/combine/index.js",
"require": "./dist/cjs/middleware/combine/index.js"
},
"./ssg": {
"types": "./dist/types/helper/ssg/index.d.ts",
"import": "./dist/helper/ssg/index.js",
Expand Down Expand Up @@ -493,6 +498,9 @@
"secure-headers": [
"./dist/types/middleware/secure-headers"
],
"combine": [
"./dist/types/middleware/combine"
],
"validator": [
"./dist/types/validator/index.d.ts"
],
Expand Down
264 changes: 264 additions & 0 deletions src/middleware/combine/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import { Hono } from '../../hono'
import { every, except, some } from '.'
import type { MiddlewareHandler } from '../../types'

const nextMiddleware: MiddlewareHandler = async (_, next) => await next()

describe('some', () => {
let app: Hono

beforeEach(() => {
app = new Hono()
})

it('Should call only the first middleware', async () => {
const middleware1 = vi.fn(nextMiddleware)
const middleware2 = vi.fn(nextMiddleware)

app.use('/', some(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
const res = await app.request('http://localhost/')

expect(middleware1).toBeCalled()
expect(middleware2).not.toBeCalled()
expect(await res.text()).toBe('Hello World')
})

it('Should try to call the second middleware if the first one throws an error', async () => {
const middleware1 = () => {
throw new Error('Error')
}
const middleware2 = vi.fn(nextMiddleware)

app.use('/', some(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
const res = await app.request('http://localhost/')

expect(middleware2).toBeCalled()
expect(await res.text()).toBe('Hello World')
})

it('Should try to call the second middleware if the first one returns false', async () => {
const middleware1 = () => false
const middleware2 = vi.fn(nextMiddleware)

app.use('/', some(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
const res = await app.request('http://localhost/')

expect(middleware2).toBeCalled()
expect(await res.text()).toBe('Hello World')
})

it('Should throw last error if all middleware throw an error', async () => {
const middleware1 = () => {
throw new Error('Error1')
}
const middleware2 = () => {
throw new Error('Error2')
}

app.use('/', some(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
app.onError((error, c) => {
return c.text(error.message)
})
const res = await app.request('http://localhost/')

expect(await res.text()).toBe('Error2')
})

it('Should throw error if all middleware return false', async () => {
const middleware1 = () => false
const middleware2 = () => false

app.use('/', some(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
app.onError((_, c) => {
return c.text('oops')
})
const res = await app.request('http://localhost/')

expect(await res.text()).toBe('oops')
})
})

describe('every', () => {
let app: Hono

beforeEach(() => {
app = new Hono()
})

it('Should call all middleware', async () => {
const middleware1 = vi.fn(nextMiddleware)
const middleware2 = vi.fn(nextMiddleware)

app.use('/', every(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
const res = await app.request('http://localhost/')

expect(middleware1).toBeCalled()
expect(middleware2).toBeCalled()
expect(await res.text()).toBe('Hello World')
})

it('Should throw error if any middleware throws an error', async () => {
const middleware1 = () => {
throw new Error('Error1')
}
const middleware2 = vi.fn(nextMiddleware)

app.use('/', every(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
app.onError((error, c) => {
return c.text(error.message)
})
const res = await app.request('http://localhost/')

expect(await res.text()).toBe('Error1')
expect(middleware2).not.toBeCalled()
})

it('Should throw error if any middleware returns false', async () => {
const middleware1 = () => false
const middleware2 = vi.fn(nextMiddleware)

app.use('/', every(middleware1, middleware2))
app.get('/', (c) => {
return c.text('Hello World')
})
app.onError((_, c) => {
return c.text('oops')
})
const res = await app.request('http://localhost/')

expect(await res.text()).toBe('oops')
expect(middleware2).not.toBeCalled()
})
})

describe('except', () => {
let app: Hono

beforeEach(() => {
app = new Hono()
})

it('Should call all middleware, except the one that matches the condition', async () => {
const middleware1 = vi.fn(nextMiddleware)
const middleware2 = vi.fn(nextMiddleware)

app.use('*', except('/maintenance', middleware1, middleware2))
app.get('/maintenance', (c) => {
return c.text('Hello Maintenance')
})
app.get('*', (c) => {
return c.redirect('/maintenance')
})
let res = await app.request('http://localhost/')

expect(middleware1).toBeCalled()
expect(middleware2).toBeCalled()
expect(res.headers.get('location')).toBe('/maintenance')

middleware1.mockClear()
middleware2.mockClear()
res = await app.request('http://localhost/maintenance')

expect(middleware1).not.toBeCalled()
expect(middleware2).not.toBeCalled()
expect(await res.text()).toBe('Hello Maintenance')
})

it('Should call all middleware, except the one that matches some of the conditions', async () => {
const middleware1 = vi.fn(nextMiddleware)
const middleware2 = vi.fn(nextMiddleware)

app.use('*', except(['/maintenance', '/public/users/:id'], middleware1, middleware2))
app.get('/maintenance', (c) => {
return c.text('Hello Maintenance')
})
app.get('/public/users/:id', (c) => {
return c.text(`Hello Public User ${c.req.param('id')}`)
})
app.get('/secret', (c) => {
return c.text('Hello Secret')
})
let res = await app.request('http://localhost/secret')

expect(middleware1).toBeCalled()
expect(middleware2).toBeCalled()
expect(await res.text()).toBe('Hello Secret')

middleware1.mockClear()
middleware2.mockClear()
res = await app.request('http://localhost/maintenance')

expect(middleware1).not.toBeCalled()
expect(middleware2).not.toBeCalled()
expect(await res.text()).toBe('Hello Maintenance')

middleware1.mockClear()
middleware2.mockClear()
res = await app.request('http://localhost/public/users/123')

expect(middleware1).not.toBeCalled()
expect(middleware2).not.toBeCalled()
expect(await res.text()).toBe('Hello Public User 123')
})

it('Should call all middleware, except the one that matches some of the condition function', async () => {
const middleware1 = vi.fn(nextMiddleware)
const middleware2 = vi.fn(nextMiddleware)

app.use(
'*',
except(['/maintenance', (c) => !!c.req.path.match(/public/)], middleware1, middleware2)
)
app.get('/maintenance', (c) => {
return c.text('Hello Maintenance')
})
app.get('/public/users/:id', (c) => {
return c.text(`Hello Public User ${c.req.param('id')}`)
})
app.get('/secret', (c) => {
return c.text('Hello Secret')
})
let res = await app.request('http://localhost/secret')

expect(middleware1).toBeCalled()
expect(middleware2).toBeCalled()
expect(await res.text()).toBe('Hello Secret')

middleware1.mockClear()
middleware2.mockClear()
res = await app.request('http://localhost/maintenance')

expect(middleware1).not.toBeCalled()
expect(middleware2).not.toBeCalled()
expect(await res.text()).toBe('Hello Maintenance')

middleware1.mockClear()
middleware2.mockClear()
res = await app.request('http://localhost/public/users/123')

expect(middleware1).not.toBeCalled()
expect(middleware2).not.toBeCalled()
expect(await res.text()).toBe('Hello Public User 123')
})
})
Loading

0 comments on commit 9a6e52d

Please sign in to comment.