Start routing and pipeline rewrite
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2022-01-17 15:57:40 -06:00
parent 9b8333295f
commit 8cf19792a6
34 changed files with 470 additions and 1654 deletions

View File

@@ -11,6 +11,7 @@ import {Controller} from '../Controller'
import {Middlewares} from '../../service/Middlewares'
import {Middleware} from './Middleware'
import {Config} from '../../service/Config'
import {Validator} from '../../validation/Validator'
/**
* Type alias for an item that is a valid response object, or lack thereof.
@@ -66,7 +67,7 @@ export class Route extends AppClass {
}
/**
* Load and compile all of the registered routes and their groups, accounting
* Load and compile all the registered routes and their groups, accounting
* for nested groups and resolving handlers.
*
* This function attempts to resolve the route handlers ahead of time to cache
@@ -215,6 +216,8 @@ export class Route extends AppClass {
/** Pre-compiled route handler for the main route handler for this route. */
protected compiledPostflight?: ResolvedRouteHandler[]
protected validator?: Validator<unknown>
/** Programmatic aliases of this route. */
public aliases: string[] = []
@@ -387,6 +390,15 @@ export class Route extends AppClass {
return this
}
input(validator: Validator<any>): this {
if ( !this.validator ) {
//
}
this.validator = validator
return this
}
/** Prefix the route's path with the given prefix, normalizing `/` characters. */
private prepend(prefix: string): this {
if ( !prefix.endsWith('/') ) {

209
src/http/routing/Route2.ts Normal file
View File

@@ -0,0 +1,209 @@
import {Collection, Either, PrefixTypeArray} from '../../util'
import {ResponseFactory} from '../response/ResponseFactory'
import {HTTPMethod, Request} from '../lifecycle/Request'
import {TypedDependencyKey, constructable, Constructable, Instantiable} from '../../di'
import {Middleware} from './Middleware'
/**
* Type alias for an item that is a valid response object, or lack thereof.
*/
export type ResponseObject = ResponseFactory | string | number | void | any | Promise<ResponseObject>
/**
* Type alias for a function that applies a route handler to the request.
* The goal is to transform RouteHandlers to ResolvedRouteHandler.
*/
export type ResolvedRouteHandler = (request: Request) => ResponseObject
export type ParameterProvidingMiddleware<T> = (request: Request) => Either<ResponseObject, T>
export interface HandledRoute<TReturn extends ResponseObject, THandlerParams extends unknown[] = []> {
/**
* Set a programmatic name for this route.
* @param name
*/
alias(name: string): this
}
export class Route<TReturn extends ResponseObject, THandlerParams extends unknown[] = []> {
protected preflight: Collection<ResolvedRouteHandler> = new Collection<ResolvedRouteHandler>()
protected parameters: Collection<ParameterProvidingMiddleware<unknown>> = new Collection<ParameterProvidingMiddleware<unknown>>()
protected postflight: Collection<ResolvedRouteHandler> = new Collection<ResolvedRouteHandler>()
protected aliases: Collection<string> = new Collection<string>()
protected handler?: Constructable<(...x: THandlerParams) => TReturn>
constructor(
protected method: HTTPMethod | HTTPMethod[],
protected route: string,
) {}
/**
* Set a programmatic name for this route.
* @param name
*/
public alias(name: string): this {
this.aliases.push(name)
return this
}
/**
* Get the string-form of the route.
*/
public getRoute(): string {
return this.route
}
/**
* Get the string-form methods supported by the route.
*/
public getMethods(): HTTPMethod[] {
if ( !Array.isArray(this.method) ) {
return [this.method]
}
return this.method
}
/**
* Get preflight middleware for this route.
*/
public getPreflight(): Collection<ResolvedRouteHandler> {
return this.preflight.clone()
}
/**
* Get postflight middleware for this route.
*/
public getPostflight(): Collection<ResolvedRouteHandler> {
return this.postflight.clone()
}
/**
* Returns true if this route matches the given HTTP verb and request path.
* @param method
* @param potential
*/
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 Boolean(this.extract(potential))
}
/**
* Given a request path, try to extract this route's paramters from the path string.
*
* @example
* For route `/foo/:bar/baz` and input `/foo/bob/baz`, extracts:
*
* ```typescript
* {
* bar: 'bob'
* }
* ```
*
* @param 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
}
public parameterMiddleware<T>(
handler: ParameterProvidingMiddleware<T>,
): Route<TReturn, PrefixTypeArray<T, THandlerParams>> {
const route = new Route<TReturn, PrefixTypeArray<T, THandlerParams>>(
this.method,
this.route,
)
route.copyFrom(this)
route.parameters.push(handler)
return route
}
private copyFrom(other: Route<TReturn, any>) {
this.preflight = other.preflight.clone()
this.postflight = other.postflight.clone()
this.aliases = other.aliases.clone()
}
public calls<TKey>(
key: TypedDependencyKey<TKey>,
selector: (x: TKey) => (...params: THandlerParams) => TReturn,
): HandledRoute<TReturn, THandlerParams> {
this.handler = constructable<TKey>(key)
.tap(inst => Function.prototype.bind.call(inst as any, selector(inst)) as ((...params: THandlerParams) => TReturn))
return this
}
public pre(middleware: Instantiable<Middleware>): this {
this.preflight.push(request => request.make<Middleware>(middleware).apply())
return this
}
public post(middleware: Instantiable<Middleware>): this {
this.postflight.push(request => request.make<Middleware>(middleware).apply())
return this
}
// validator
/** Cast the route to an intelligible string. */
toString(): string {
const method = Array.isArray(this.method) ? this.method : [this.method]
return `${method.join('|')} -> ${this.route}`
}
/** Prefix the route's path with the given prefix, normalizing `/` characters. */
private prepend(prefix: string): this {
if ( !prefix.endsWith('/') ) {
prefix = `${prefix}/`
}
if ( this.route.startsWith('/') ) {
this.route = this.route.substring(1)
}
this.route = `${prefix}${this.route}`
return this
}
}