Add mounting for activated routes, route compiling, routing
This commit is contained in:
141
src/http/routing/Route.ts
Normal file
141
src/http/routing/Route.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import {AppClass} from "../../lifecycle/AppClass";
|
||||
import {HTTPMethod, Request} from "../lifecycle/Request";
|
||||
import {Application} from "../../lifecycle/Application";
|
||||
import {RouteGroup} from "./RouteGroup";
|
||||
|
||||
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
|
||||
|
||||
export class Route extends AppClass {
|
||||
private static registeredRoutes: Route[] = []
|
||||
private static registeredGroups: RouteGroup[] = []
|
||||
|
||||
private static compiledGroupStack: RouteGroup[] = []
|
||||
|
||||
public static registerGroup(group: RouteGroup) {
|
||||
this.registeredGroups.push(group)
|
||||
}
|
||||
|
||||
public static async compile(): Promise<Route[]> {
|
||||
let registeredRoutes = this.registeredRoutes
|
||||
const registeredGroups = this.registeredGroups
|
||||
|
||||
this.registeredRoutes = []
|
||||
this.registeredGroups = []
|
||||
|
||||
const stack = [...this.compiledGroupStack].reverse()
|
||||
for ( const route of registeredRoutes ) {
|
||||
for ( const group of stack ) {
|
||||
route.prepend(group.prefix)
|
||||
}
|
||||
}
|
||||
|
||||
for ( const group of registeredGroups ) {
|
||||
this.compiledGroupStack.push(group)
|
||||
await group.group()
|
||||
|
||||
const childCompilation = await this.compile()
|
||||
registeredRoutes = registeredRoutes.concat(childCompilation)
|
||||
|
||||
this.compiledGroupStack.pop()
|
||||
}
|
||||
|
||||
return registeredRoutes
|
||||
}
|
||||
|
||||
public static endpoint(method: HTTPMethod | HTTPMethod[], definition: string, handler: RouteHandler) {
|
||||
const route = new Route(method, handler, definition)
|
||||
this.registeredRoutes.push(route)
|
||||
return route
|
||||
}
|
||||
|
||||
public static get(definition: string, handler: RouteHandler) {
|
||||
return this.endpoint('get', definition, handler)
|
||||
}
|
||||
|
||||
public static post(definition: string, handler: RouteHandler) {
|
||||
return this.endpoint('post', definition, handler)
|
||||
}
|
||||
|
||||
public static put(definition: string, handler: RouteHandler) {
|
||||
return this.endpoint('put', definition, handler)
|
||||
}
|
||||
|
||||
public static patch(definition: string, handler: RouteHandler) {
|
||||
return this.endpoint('patch', definition, handler)
|
||||
}
|
||||
|
||||
public static delete(definition: string, handler: RouteHandler) {
|
||||
return this.endpoint('delete', definition, handler)
|
||||
}
|
||||
|
||||
public static any(definition: string, handler: RouteHandler) {
|
||||
return this.endpoint(['get', 'put', 'patch', 'post', 'delete'], definition, handler)
|
||||
}
|
||||
|
||||
public static group(prefix: string, group: () => void | Promise<void>) {
|
||||
const grp = <RouteGroup> Application.getApplication().make(RouteGroup, group, prefix)
|
||||
this.registeredGroups.push(grp)
|
||||
return grp
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected method: HTTPMethod | HTTPMethod[],
|
||||
protected handler: RouteHandler,
|
||||
protected route: string
|
||||
) { super() }
|
||||
|
||||
public match(method: HTTPMethod, potential: string): boolean {
|
||||
if ( Array.isArray(this.method) && !this.method.includes(method) ) return false
|
||||
else if ( !Array.isArray(this.method) && this.method !== method ) return false
|
||||
|
||||
return !!this.extract(potential)
|
||||
}
|
||||
|
||||
public extract(potential: string): {[key: string]: string} | undefined {
|
||||
const routeParts = (this.route.startsWith('/') ? this.route.substr(1) : this.route).split('/')
|
||||
const potentialParts = (potential.startsWith('/') ? potential.substr(1) : potential).split('/')
|
||||
|
||||
const params: any = {}
|
||||
let wildcardIdx = 0
|
||||
|
||||
for ( let i = 0; i < routeParts.length; i += 1 ) {
|
||||
const part = routeParts[i]
|
||||
|
||||
if ( part === '**' ) {
|
||||
params[wildcardIdx] = potentialParts.slice(i).join('/')
|
||||
return params
|
||||
}
|
||||
|
||||
if ( (potentialParts.length - 1) < i ) {
|
||||
return
|
||||
}
|
||||
|
||||
if ( part === '*' ) {
|
||||
params[wildcardIdx] = potentialParts[i]
|
||||
wildcardIdx += 1
|
||||
} else if ( part.startsWith(':') ) {
|
||||
params[part.substr(1)] = potentialParts[i]
|
||||
} else if ( potentialParts[i] !== part ) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, we didn't find a **
|
||||
// So, if the lengths are different, fail
|
||||
if ( routeParts.length !== potentialParts.length ) return
|
||||
return params
|
||||
}
|
||||
|
||||
private prepend(prefix: string) {
|
||||
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
||||
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
||||
this.route = `${prefix}${this.route}`
|
||||
}
|
||||
|
||||
toString() {
|
||||
const method = Array.isArray(this.method) ? this.method : [this.method]
|
||||
return `${method.join('|')} -> ${this.route}`
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user