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 {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 {ErrorWithContext} from "@extollo/util";
|
||||||
import {Route} from "./Route";
|
import {ResolvedRouteHandler, Route} from "./Route";
|
||||||
|
|
||||||
export class ActivatedRoute {
|
export class ActivatedRoute {
|
||||||
public readonly params: {[key: string]: string}
|
public readonly params: {[key: string]: string}
|
||||||
|
public readonly handler: ResolvedRouteHandler
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly route: Route,
|
public readonly route: Route,
|
||||||
@ -19,5 +20,6 @@ export class ActivatedRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.params = params
|
this.params = params
|
||||||
|
this.handler = route.resolveHandler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,16 @@ import {AppClass} from "../../lifecycle/AppClass";
|
|||||||
import {HTTPMethod, Request} from "../lifecycle/Request";
|
import {HTTPMethod, Request} from "../lifecycle/Request";
|
||||||
import {Application} from "../../lifecycle/Application";
|
import {Application} from "../../lifecycle/Application";
|
||||||
import {RouteGroup} from "./RouteGroup";
|
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
|
// 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 ) {
|
for ( const group of stack ) {
|
||||||
route.prepend(group.prefix)
|
route.prepend(group.prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
route.resolveHandler() // Try to resolve here to catch any errors at boot-time
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( const group of registeredGroups ) {
|
for ( const group of registeredGroups ) {
|
||||||
@ -82,7 +92,7 @@ export class Route extends AppClass {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected method: HTTPMethod | HTTPMethod[],
|
protected method: HTTPMethod | HTTPMethod[],
|
||||||
protected handler: RouteHandler,
|
protected readonly handler: RouteHandler,
|
||||||
protected route: string
|
protected route: string
|
||||||
) { super() }
|
) { super() }
|
||||||
|
|
||||||
@ -128,6 +138,47 @@ export class Route extends AppClass {
|
|||||||
return params
|
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) {
|
private prepend(prefix: string) {
|
||||||
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
||||||
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {Application} from './Application';
|
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.
|
* Base type for a class that supports binding methods by string.
|
||||||
@ -37,6 +37,10 @@ export class AppClass {
|
|||||||
return this.appClassApplication;
|
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.
|
* Get the method with the given name from this class, bound to this class.
|
||||||
* @param {string} methodName
|
* @param {string} methodName
|
||||||
|
@ -105,7 +105,7 @@ export abstract class Canonical<T> extends Unit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async initCanonicalItem(definition: CanonicalDefinition): Promise<T> {
|
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> {
|
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)
|
return this.app().make(definition.imported.default)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isInstantiable(definition.imported[definition.canonicalName]) ) {
|
if ( isInstantiable(definition.imported[definition.canonicalName.split(':').reverse()[0]]) ) {
|
||||||
return this.app().make(definition.imported[definition.canonicalName])
|
return this.app().make(definition.imported[definition.canonicalName.split(':').reverse()[0]])
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidCanonicalExportError(definition.originalName)
|
throw new InvalidCanonicalExportError(definition.originalName)
|
||||||
|
@ -8,8 +8,8 @@ export class CanonicalStatic<T, T2> extends Canonical<StaticClass<T, T2>> {
|
|||||||
return definition.imported.default
|
return definition.imported.default
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isStaticClass(definition.imported[definition.canonicalName]) ) {
|
if ( isStaticClass(definition.imported[definition.canonicalName.split(':').reverse()[0]]) ) {
|
||||||
return definition.imported[definition.canonicalName]
|
return definition.imported[definition.canonicalName.split(':').reverse()[0]]
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidCanonicalExportError(definition.originalName)
|
throw new InvalidCanonicalExportError(definition.originalName)
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import {CanonicalInstantiable} from "./CanonicalInstantiable";
|
import {CanonicalStatic} from "./CanonicalStatic";
|
||||||
import {Singleton} from "@extollo/di";
|
import {Singleton, Instantiable} from "@extollo/di";
|
||||||
import {Controller} from "../http/Controller";
|
import {Controller} from "../http/Controller";
|
||||||
import {CanonicalDefinition} from "./Canonical";
|
import {CanonicalDefinition} from "./Canonical";
|
||||||
|
|
||||||
@Singleton()
|
@Singleton()
|
||||||
export class Controllers extends CanonicalInstantiable<Controller> {
|
export class Controllers extends CanonicalStatic<Instantiable<Controller>, Controller> {
|
||||||
protected appPath = ['http', 'controllers']
|
protected appPath = ['http', 'controllers']
|
||||||
protected canonicalItem = 'controller'
|
protected canonicalItem = 'controller'
|
||||||
protected suffix = '.controller.js'
|
protected suffix = '.controller.js'
|
||||||
|
|
||||||
public async initCanonicalItem(definition: CanonicalDefinition) {
|
public async initCanonicalItem(definition: CanonicalDefinition) {
|
||||||
const item = await super.initCanonicalItem(definition)
|
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.`)
|
throw new TypeError(`Invalid controller definition: ${definition.originalName}. Controllers must extend from @extollo/lib.http.Controller.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user