Skip to content

Commit

Permalink
fix(express): dedicated reflet express error
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyben committed Oct 4, 2021
1 parent dd208d7 commit ddf9c90
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 13 deletions.
13 changes: 10 additions & 3 deletions express/__tests__/routing.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as supertest from 'supertest'
import * as express from 'express'
import { register, Router, Get, Post, Patch, Route, Res, Req, Use, Params, Body } from '../src'
import { RefletExpressError } from '../src/reflet-error'
import { log } from '../../testing/tools'

describe('basic routing', () => {
Expand Down Expand Up @@ -190,7 +191,9 @@ describe('children routers', () => {

const app = express()

expect(() => register(app, [Foo])).toThrowError(/@Router/)
expect(() => register(app, [Foo])).toThrow(
expect.objectContaining({ code: <RefletExpressError['code']>'ROUTER_DECORATOR_MISSING' })
)
})
})

Expand Down Expand Up @@ -339,7 +342,9 @@ describe('constrain with path-router objects', () => {
@Router.Children(() => [Items])
class Foo {}

expect(() => register(express(), [Foo])).toThrow(/dynamic/)
expect(() => register(express(), [Foo])).toThrow(
expect.objectContaining({ code: <RefletExpressError['code']>'DYNAMIC_ROUTER_PATH_UNDEFINED' })
)
})
})
// tslint:enable: no-empty
Expand All @@ -351,5 +356,7 @@ test('route function constraint', async () => {
prop = 'foo'
}

expect(() => register(express(), [Foo])).toThrow(/function/)
expect(() => register(express(), [Foo])).toThrow(
expect.objectContaining({ code: <RefletExpressError['code']>'INVALID_ROUTE_TYPE' })
)
})
9 changes: 8 additions & 1 deletion express/src/application-class.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as express from 'express'
import { register } from './register'
import { ClassType, RegistrationArray } from './interfaces'
import { RefletExpressError } from './reflet-error'

/**
* @internal
Expand Down Expand Up @@ -86,7 +87,13 @@ function mixinApplication(target: express.Application, source: Function) {

for (const key of keys) {
if (key === 'constructor') continue
if (key in target) throw Error(`Cannot overwrite "${key}" on express application.`)

if (key in target) {
throw new RefletExpressError(
'EXPRESS_PROPERTY_PROTECTED',
`Cannot overwrite "${key}" on express application.`
)
}

const descriptor = Object.getOwnPropertyDescriptor(proto.prototype, key)!
Object.defineProperty(target, key, descriptor)
Expand Down
7 changes: 6 additions & 1 deletion express/src/param-decorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as express from 'express'
import { flatMapFast } from './array-manipulation'
import { ClassType, RequestHeaderName } from './interfaces'
import { RefletExpressError } from './reflet-error'

const META = Symbol('param')

Expand Down Expand Up @@ -353,7 +354,11 @@ export function createParamDecorator<T = any>(

if (params[index]) {
const codePath = `${target.constructor.name}.${key.toString()}`
throw Error(`Parameter ${index} of "${codePath}" should have a single express decorator.`)

throw new RefletExpressError(
'MULTIPLE_PARAMETER_DECORATORS',
`Parameter ${index} of "${codePath}" should have a single express decorator.`
)
}

params[index] = { mapper, use, dedupeUse }
Expand Down
21 changes: 21 additions & 0 deletions express/src/reflet-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Dedicated Reflet Error.
* @public
*/
export class RefletExpressError extends Error {
code:
| 'ROUTER_DECORATOR_MISSING'
| 'DYNAMIC_ROUTER_PATH_UNDEFINED'
| 'ROUTER_PATH_CONSTRAINED'
| 'EXPRESS_PROPERTY_PROTECTED'
| 'MULTIPLE_PARAMETER_DECORATORS'
| 'INVALID_ROUTE_TYPE'
| 'INVALID_EXPRESS_APP'

constructor(code: RefletExpressError['code'], message: string) {
super(message)
this.code = code

Error.captureStackTrace(this, RefletExpressError)
}
}
34 changes: 26 additions & 8 deletions express/src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { extractErrorHandlers } from './error-handler-decorator'
import { extractParams, extractParamsMiddlewares } from './param-decorators'
import { extractSend } from './send-decorator'
import { ApplicationMeta, extractApplicationClass } from './application-class'
import { RefletExpressError } from './reflet-error'

/**
* Main method to register routers into an express application.
Expand All @@ -36,7 +37,7 @@ import { ApplicationMeta, extractApplicationClass } from './application-class'
*/
export function register(app: express.Application, routers: RegistrationArray): express.Application {
if (!isExpressApp(app)) {
throw Error('This is not an Express application.')
throw new RefletExpressError('INVALID_EXPRESS_APP', 'This is not an Express application.')
}

const appMeta = extractApplicationClass(app)
Expand Down Expand Up @@ -86,7 +87,10 @@ function registerRouter(
const routerMeta = extractRouterMeta(routerClass)

if (!routerMeta || routerMeta.path == null) {
throw Error(`"${routerClass.name}" must be decorated with @Router.`)
throw new RefletExpressError(
'ROUTER_DECORATOR_MISSING',
`"${routerClass.name}" must be decorated with @Router.`
)
}

checkPathConstraint(constrainedPath, routerMeta, routerClass)
Expand Down Expand Up @@ -260,7 +264,10 @@ function checkPathConstraint(
) {
if (router.path === DYNAMIC_PATH) {
if (constrainedPath == null) {
throw Error(`"${routerClass.name}" is dynamic and must be registered with a path.`)
throw new RefletExpressError(
'DYNAMIC_ROUTER_PATH_UNDEFINED',
`"${routerClass.name}" is dynamic and must be registered with a path.`
)
}

// stop there if dynamic path
Expand All @@ -271,7 +278,10 @@ function checkPathConstraint(
typeof router.path === 'string' && typeof constrainedPath === 'string' && router.path !== constrainedPath

if (notSameString) {
throw Error(`"${routerClass.name}" expects "${constrainedPath}" as root path. Actual: "${router.path}".`)
throw new RefletExpressError(
'ROUTER_PATH_CONSTRAINED',
`"${routerClass.name}" expects "${constrainedPath}" as root path. Actual: "${router.path}".`
)
}

const notSameRegex =
Expand All @@ -280,21 +290,26 @@ function checkPathConstraint(
router.path.source !== constrainedPath.source

if (notSameRegex) {
throw Error(`"${routerClass.name}" expects "${constrainedPath}" as root path. Actual: "${router.path}".`)
throw new RefletExpressError(
'ROUTER_PATH_CONSTRAINED',
`"${routerClass.name}" expects "${constrainedPath}" as root path. Actual: "${router.path}".`
)
}

const shouldBeString = router.path instanceof RegExp && typeof constrainedPath === 'string'

if (shouldBeString) {
throw Error(
throw new RefletExpressError(
'ROUTER_PATH_CONSTRAINED',
`"${routerClass.name}" expects string "${constrainedPath}" as root path. Actual: "${router.path}" (regex).`
)
}

const shouldBeRegex = typeof router.path === 'string' && constrainedPath instanceof RegExp

if (shouldBeRegex) {
throw Error(
throw new RefletExpressError(
'ROUTER_PATH_CONSTRAINED',
`"${routerClass.name}" expects regex "${constrainedPath}" as root path. Actual: "${router.path}" (string).`
)
}
Expand All @@ -315,7 +330,10 @@ function createHandler(
const fn = routerInstance[key] as Function

if (typeof fn !== 'function') {
throw Error(`"${routerClass.name}.${key.toString()}" should be a function.`)
throw new RefletExpressError(
'INVALID_ROUTE_TYPE',
`"${routerClass.name}.${key.toString()}" should be a function.`
)
}

const isAsync = isAsyncFunction(fn)
Expand Down

0 comments on commit ddf9c90

Please sign in to comment.