Add logic for resolving route handlers
This commit is contained in:
parent
a9ffa771dc
commit
9747d40659
@ -1,3 +1,12 @@
|
||||
import {AppClass} from "../lifecycle/AppClass";
|
||||
import {Request} from "./lifecycle/Request";
|
||||
|
||||
export class Controller extends AppClass {}
|
||||
export class Controller extends AppClass {
|
||||
constructor(
|
||||
protected readonly request: Request
|
||||
) { super() }
|
||||
|
||||
protected container() {
|
||||
return this.request
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import {ErrorWithContext} from "@extollo/util";
|
||||
import {Route} from "./Route";
|
||||
import {ResolvedRouteHandler, Route} from "./Route";
|
||||
|
||||
export class ActivatedRoute {
|
||||
public readonly params: {[key: string]: string}
|
||||
public readonly handler: ResolvedRouteHandler
|
||||
|
||||
constructor(
|
||||
public readonly route: Route,
|
||||
@ -19,5 +20,6 @@ export class ActivatedRoute {
|
||||
}
|
||||
|
||||
this.params = params
|
||||
this.handler = route.resolveHandler()
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,16 @@ import {AppClass} from "../../lifecycle/AppClass";
|
||||
import {HTTPMethod, Request} from "../lifecycle/Request";
|
||||
import {Application} from "../../lifecycle/Application";
|
||||
import {RouteGroup} from "./RouteGroup";
|
||||
import {ResponseFactory} from "../response/ResponseFactory";
|
||||
import {Response} from "../lifecycle/Response";
|
||||
import {Controllers} from "../../service/Controllers";
|
||||
import {ErrorWithContext} from "@extollo/util";
|
||||
import {Controller} from "../Controller";
|
||||
|
||||
export type ResponseObject = ResponseFactory | string | number | void | any | Promise<ResponseObject>
|
||||
export type RouteHandler = ((request: Request, response: Response) => ResponseObject) | ((request: Request) => ResponseObject) | (() => ResponseObject) | string
|
||||
export type ResolvedRouteHandler = (request: Request) => ResponseObject
|
||||
|
||||
export type RouteHandler = (request: Request) => void | Promise<void> // FIXME want to do some improvements here
|
||||
|
||||
// TODO middleware, domains, named routes - support this on groups as well
|
||||
|
||||
@ -29,6 +37,8 @@ export class Route extends AppClass {
|
||||
for ( const group of stack ) {
|
||||
route.prepend(group.prefix)
|
||||
}
|
||||
|
||||
route.resolveHandler() // Try to resolve here to catch any errors at boot-time
|
||||
}
|
||||
|
||||
for ( const group of registeredGroups ) {
|
||||
@ -82,7 +92,7 @@ export class Route extends AppClass {
|
||||
|
||||
constructor(
|
||||
protected method: HTTPMethod | HTTPMethod[],
|
||||
protected handler: RouteHandler,
|
||||
protected readonly handler: RouteHandler,
|
||||
protected route: string
|
||||
) { super() }
|
||||
|
||||
@ -128,6 +138,47 @@ export class Route extends AppClass {
|
||||
return params
|
||||
}
|
||||
|
||||
public resolveHandler(): ResolvedRouteHandler {
|
||||
if ( typeof this.handler !== 'string' ) {
|
||||
return (request: Request) => {
|
||||
// @ts-ignore
|
||||
return this.handler(request, request.response)
|
||||
}
|
||||
} else {
|
||||
const parts = this.handler.split('.')
|
||||
if ( parts.length < 1 ) {
|
||||
const e = new ErrorWithContext('Route handler does not specify a method name.')
|
||||
e.context = {
|
||||
handler: this.handler
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
const [controllerName, methodName] = parts
|
||||
|
||||
const controllersService = <Controllers> this.make(Controllers)
|
||||
const controllerClass = controllersService.get(controllerName)
|
||||
if ( !controllerClass ) {
|
||||
const e = new ErrorWithContext('Controller not found for route handler.')
|
||||
e.context = {
|
||||
handler: this.handler,
|
||||
controllerName,
|
||||
methodName,
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
return (request: Request) => {
|
||||
// If not a function, then we got a string reference to a controller method
|
||||
// So, we need to use the request container to instantiate the controller
|
||||
// and bind the method
|
||||
const controller = <Controller> request.make(controllerClass, request)
|
||||
const method = controller.getBoundMethod(methodName)
|
||||
return method()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private prepend(prefix: string) {
|
||||
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
||||
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Application} from './Application';
|
||||
import {Container} from "@extollo/di";
|
||||
import {Container, DependencyKey} from "@extollo/di";
|
||||
|
||||
/**
|
||||
* Base type for a class that supports binding methods by string.
|
||||
@ -37,6 +37,10 @@ export class AppClass {
|
||||
return this.appClassApplication;
|
||||
}
|
||||
|
||||
protected make<T>(target: DependencyKey, ...parameters: any[]): T {
|
||||
return this.container().make<T>(target, ...parameters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method with the given name from this class, bound to this class.
|
||||
* @param {string} methodName
|
||||
|
@ -105,7 +105,7 @@ export abstract class Canonical<T> extends Unit {
|
||||
}
|
||||
|
||||
public async initCanonicalItem(definition: CanonicalDefinition): Promise<T> {
|
||||
return definition.imported.default ?? definition.imported[definition.canonicalName]
|
||||
return definition.imported.default ?? definition.imported[definition.canonicalName.split(':').reverse()[0]]
|
||||
}
|
||||
|
||||
protected async buildCanonicalDefinition(filePath: string): Promise<CanonicalDefinition> {
|
||||
|
@ -17,8 +17,8 @@ export class CanonicalInstantiable<T> extends Canonical<Instantiable<T>> {
|
||||
return this.app().make(definition.imported.default)
|
||||
}
|
||||
|
||||
if ( isInstantiable(definition.imported[definition.canonicalName]) ) {
|
||||
return this.app().make(definition.imported[definition.canonicalName])
|
||||
if ( isInstantiable(definition.imported[definition.canonicalName.split(':').reverse()[0]]) ) {
|
||||
return this.app().make(definition.imported[definition.canonicalName.split(':').reverse()[0]])
|
||||
}
|
||||
|
||||
throw new InvalidCanonicalExportError(definition.originalName)
|
||||
|
@ -8,8 +8,8 @@ export class CanonicalStatic<T, T2> extends Canonical<StaticClass<T, T2>> {
|
||||
return definition.imported.default
|
||||
}
|
||||
|
||||
if ( isStaticClass(definition.imported[definition.canonicalName]) ) {
|
||||
return definition.imported[definition.canonicalName]
|
||||
if ( isStaticClass(definition.imported[definition.canonicalName.split(':').reverse()[0]]) ) {
|
||||
return definition.imported[definition.canonicalName.split(':').reverse()[0]]
|
||||
}
|
||||
|
||||
throw new InvalidCanonicalExportError(definition.originalName)
|
||||
|
@ -1,17 +1,17 @@
|
||||
import {CanonicalInstantiable} from "./CanonicalInstantiable";
|
||||
import {Singleton} from "@extollo/di";
|
||||
import {CanonicalStatic} from "./CanonicalStatic";
|
||||
import {Singleton, Instantiable} from "@extollo/di";
|
||||
import {Controller} from "../http/Controller";
|
||||
import {CanonicalDefinition} from "./Canonical";
|
||||
|
||||
@Singleton()
|
||||
export class Controllers extends CanonicalInstantiable<Controller> {
|
||||
export class Controllers extends CanonicalStatic<Instantiable<Controller>, Controller> {
|
||||
protected appPath = ['http', 'controllers']
|
||||
protected canonicalItem = 'controller'
|
||||
protected suffix = '.controller.js'
|
||||
|
||||
public async initCanonicalItem(definition: CanonicalDefinition) {
|
||||
const item = await super.initCanonicalItem(definition)
|
||||
if ( !(item instanceof Controller) ) {
|
||||
if ( !(item.prototype instanceof Controller) ) {
|
||||
throw new TypeError(`Invalid controller definition: ${definition.originalName}. Controllers must extend from @extollo/lib.http.Controller.`)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user