diff --git a/src/Controller.ts b/src/Controller.ts index abdd228..7a26ad6 100644 --- a/src/Controller.ts +++ b/src/Controller.ts @@ -9,6 +9,9 @@ import Middleware, {MiddlewareType} from "./Middleware"; import Application from "./Application"; export default abstract class Controller { + /** + * TODO: this should not be static, it should actually be bound to an app instance. + */ private static readonly routes: { [p: string]: string | undefined } = {}; public static route( @@ -20,11 +23,12 @@ export default abstract class Controller { let path = this.routes[route]; if (path === undefined) throw new Error(`Unknown route for name ${route}.`); + const regExp = this.getRouteParamRegExp('[a-zA-Z0-9_-]+', 'g'); if (typeof params === 'string' || typeof params === 'number') { - path = path.replace(/:[a-zA-Z_-]+\??/g, '' + params); + path = path.replace(regExp, '' + params); } else if (Array.isArray(params)) { let i = 0; - for (const match of path.matchAll(/:[a-zA-Z_-]+(\(.*\))?\??/g)) { + for (const match of path.matchAll(regExp)) { if (match.length > 0) { path = path.replace(match[0], typeof params[i] !== 'undefined' ? params[i] : ''); } @@ -33,7 +37,7 @@ export default abstract class Controller { path = path.replace(/\/+/g, '/'); } else { for (const key of Object.keys(params)) { - path = path.replace(new RegExp(`:${key}\\??`), params[key]); + path = path.replace(this.getRouteParamRegExp(key), params[key].toString()); } } @@ -41,6 +45,10 @@ export default abstract class Controller { return `${absolute ? config.get<string>('public_url') : ''}${path}` + (queryStr.length > 0 ? '?' + queryStr : ''); } + private static getRouteParamRegExp(key: string, flags?: string): RegExp { + return new RegExp(`:${key}(\\(.+?\\))?\\??`, flags); + } + private readonly router: Router = express.Router(); private readonly fileUploadFormRouter: Router = express.Router(); private app?: Application; @@ -194,4 +202,4 @@ export default abstract class Controller { } } -export type RouteParams = { [p: string]: string } | string[] | string | number; +export type RouteParams = { [p: string]: string | number } | string[] | string | number; diff --git a/test/Controller.test.ts b/test/Controller.test.ts new file mode 100644 index 0000000..e296a68 --- /dev/null +++ b/test/Controller.test.ts @@ -0,0 +1,35 @@ +import Controller from "../src/Controller"; + +describe('Controller.route()', () => { + new class extends Controller { + public routes(): void { + const emptyHandler = () => { + // + }; + this.get('/test/no-param', emptyHandler, 'test.no-param'); + this.get('/test/no-param/', emptyHandler, 'test.no-param-slash'); + this.get('/test/one-param/:param', emptyHandler, 'test.one-param'); + this.get('/test/two-params/:param1/:param2', emptyHandler, 'test.two-params'); + this.get('/test/paginated/:page([0-9]+)?', emptyHandler, 'test.paginated'); + this.get('/test/slug/:slug([a-zA-Z0-9]+)?', emptyHandler, 'test.slug'); + } + }().setupRoutes(); + + test('Empty params', () => { + expect(Controller.route('test.no-param')).toBe('/test/no-param'); + expect(Controller.route('test.no-param-slash')).toBe('/test/no-param/'); + expect(Controller.route('test.one-param')).toBe('/test/one-param/'); + expect(Controller.route('test.two-params')).toBe('/test/two-params/'); + expect(Controller.route('test.paginated')).toBe('/test/paginated/'); + expect(Controller.route('test.slug')).toBe('/test/slug/'); + }); + + test('Populated params', () => { + expect(Controller.route('test.no-param')).toBe('/test/no-param'); + expect(Controller.route('test.no-param-slash')).toBe('/test/no-param/'); + expect(Controller.route('test.one-param', {param: 'value'})).toBe('/test/one-param/value'); + expect(Controller.route('test.two-params', {param1: 'value1', param2: 'value2'})).toBe('/test/two-params/value1/value2'); + expect(Controller.route('test.paginated', {page: 5})).toBe('/test/paginated/5'); + expect(Controller.route('test.slug', {slug: 'abc'})).toBe('/test/slug/abc'); + }); +});