Fix complex route matching; add api controller; api standard helpers

This commit is contained in:
garrettmills 2020-07-29 23:18:38 -05:00
parent 0764085ef9
commit 0e5a0a5a5c
No known key found for this signature in database
GPG Key ID: 6ACD58D6ADACFC6E
7 changed files with 133 additions and 15 deletions

View File

@ -0,0 +1,25 @@
import BaseApiController from "../../../lib/src/http/ApiController.ts";
import {Request} from "../../../lib/src/http/Request.ts";
import {redirect} from "../../../lib/src/http/response/helpers.ts";
export default class ApiController extends BaseApiController {
get_good(request: Request) {
return {
session_key: request.session.get_key(),
}
}
get_arr(request: Request) {
return ['alpha', 'bravo', 'charlie', 'delta', 'echo']
}
get_msg(request: Request) {
return 'Hello, world!'
}
get_redirect(request: Request) {
return redirect('/home')
}
}

View File

@ -0,0 +1,41 @@
import Controller from "./Controller.ts";
import {Request} from "./Request.ts";
import ResponseFactory from "./response/ResponseFactory.ts";
import * as api from '../support/api.ts'
import JSONResponseFactory from "./response/JSONResponseFactory.ts";
export default class ApiController extends Controller {
public get_bound_method(method_name: string): (...args: any[]) => any {
// @ts-ignore
if ( typeof this[method_name] !== 'function' ) {
throw new TypeError(`Attempt to get bound method for non-function type: ${method_name}`)
}
return async (...args: any[]): Promise<any> => {
if ( args.length !== 1 ) {
// @ts-ignore
return this[method_name](...args)
}
const request: Request = args[0]
// @ts-ignore
const result = await this[method_name](request)
// If we have a request or a response factory, return that
if ( result instanceof Request || result instanceof ResponseFactory ) {
return result
}
console.log('result', result)
// Otherwise, try to package the results as an API response
if ( Array.isArray(result) ) {
return this.make(JSONResponseFactory, api.many(result))
} else if ( typeof result === 'string' ) {
return this.make(JSONResponseFactory, api.message(result))
} else {
return this.make(JSONResponseFactory, api.one(result))
}
}
}
}

View File

@ -1,5 +1,6 @@
import ResponseFactory from './ResponseFactory.ts' import ResponseFactory from './ResponseFactory.ts'
import {Request} from '../Request.ts' import {Request} from '../Request.ts'
import * as api from '../../support/api.ts'
export default class ErrorResponseFactory extends ResponseFactory { export default class ErrorResponseFactory extends ResponseFactory {
protected target_mode: 'json' | 'html' = 'html' protected target_mode: 'json' | 'html' = 'html'
@ -22,10 +23,10 @@ export default class ErrorResponseFactory extends ResponseFactory {
public async write(request: Request): Promise<Request> { public async write(request: Request): Promise<Request> {
request = await super.write(request) request = await super.write(request)
if ( this.output === 'json' ) { if ( this.target_mode === 'json' ) {
request.response.headers.set('Content-Type', 'application/json') request.response.headers.set('Content-Type', 'application/json')
request.response.body = this.build_json(this.error) request.response.body = this.build_json(this.error)
} else if ( this.output === 'html' ) { } else if ( this.target_mode === 'html' ) {
request.response.headers.set('Content-Type', 'text/html') request.response.headers.set('Content-Type', 'text/html')
request.response.body = this.build_html(this.error) request.response.body = this.build_html(this.error)
} }
@ -47,13 +48,6 @@ Stack trace:
} }
protected build_json(error: Error) { protected build_json(error: Error) {
return JSON.stringify({ return JSON.stringify(api.error(error))
success: false,
error: {
name: error.name,
message: error.message,
stack: error.stack ? error.stack.split(/\s+at\s+/).slice(1) : []
}
})
} }
} }

View File

@ -2,7 +2,7 @@ import ResponseFactory from './ResponseFactory.ts'
import {Request} from '../Request.ts' import {Request} from '../Request.ts'
export default class TemporaryRedirectResponseFactory extends ResponseFactory { export default class TemporaryRedirectResponseFactory extends ResponseFactory {
protected target_status: number = 301 protected target_status: number = 302
constructor( constructor(
public readonly destination: string, public readonly destination: string,

View File

@ -4,9 +4,14 @@ import {make} from '../../../../di/src/global.ts'
export class ComplexRoute extends Route { export class ComplexRoute extends Route {
public match(incoming: string): boolean { public match(incoming: string): boolean {
const base_length = this.split(this.base).length const base_parts = this.split(this.base)
const incoming_length = this.split(incoming).length const incoming_parts = this.split(incoming)
return base_length === incoming_length // FIXME match!
if ( base_parts.length !== incoming_parts.length ) return false
return base_parts.every((base, idx) => {
return base.toLowerCase() === incoming_parts[idx].toLowerCase()
})
} }
public build_parameters(incoming: string): RouteParameters { public build_parameters(incoming: string): RouteParameters {

View File

@ -2,7 +2,7 @@ import {Route, RouteParameters} from './Route.ts'
export class SimpleRoute extends Route { export class SimpleRoute extends Route {
public match(incoming: string): boolean { public match(incoming: string): boolean {
return incoming === this.base return incoming.toLowerCase() === this.base.toLowerCase()
} }
public build_parameters(incoming: string): RouteParameters { public build_parameters(incoming: string): RouteParameters {

53
lib/src/support/api.ts Normal file
View File

@ -0,0 +1,53 @@
export interface APIResponse {
success: boolean,
message?: string,
data?: any,
error?: {
name: string,
message: string,
stack?: string[],
}
}
export function message(message: string): APIResponse {
return {
success: true,
message,
}
}
export function one(record: any): APIResponse {
return {
success: true,
data: record,
}
}
export function many(records: any[]): APIResponse {
return {
success: true,
data: {
records,
total: records.length,
},
}
}
export function error(error: string | Error): APIResponse {
if ( typeof error === 'string' ) {
return {
success: false,
message: error,
}
} else {
return {
success: false,
message: error.message,
error: {
name: error.name,
message: error.message,
stack: error.stack ? error.stack.split(/\s+at\s+/).slice(1) : [],
},
}
}
}