Add logic for resolving route handlers

r0.1.5
Garrett Mills 3 years ago
parent a9ffa771dc
commit 9747d40659
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246

@ -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…
Cancel
Save