parent
3acc1bc83e
commit
a9ffa771dc
@ -0,0 +1,11 @@
|
|||||||
|
import {ErrorWithContext, HTTPStatus, HTTPMessage} from "@extollo/util"
|
||||||
|
|
||||||
|
export class HTTPError extends ErrorWithContext {
|
||||||
|
constructor(
|
||||||
|
public readonly status: HTTPStatus = 500,
|
||||||
|
public readonly message: string = ''
|
||||||
|
) {
|
||||||
|
super(message || HTTPMessage[status])
|
||||||
|
this.message = message || HTTPMessage[status]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import {ResponseFactory} from "./ResponseFactory"
|
||||||
|
import {Rehydratable} from "@extollo/util"
|
||||||
|
import {Request} from "../lifecycle/Request";
|
||||||
|
|
||||||
|
export function dehydrate(value: Rehydratable): DehydratedStateResponseFactory {
|
||||||
|
return new DehydratedStateResponseFactory(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DehydratedStateResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly rehydratable: Rehydratable
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
public async write(request: Request) {
|
||||||
|
request = await super.write(request)
|
||||||
|
request.response.body = JSON.stringify(this.rehydratable.dehydrate())
|
||||||
|
request.response.setHeader('Content-Type', 'application/json')
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
import {ResponseFactory} from "./ResponseFactory"
|
||||||
|
import {ErrorWithContext, HTTPStatus} from "@extollo/util"
|
||||||
|
import {Request} from "../lifecycle/Request";
|
||||||
|
import * as api from "./api"
|
||||||
|
|
||||||
|
export function error(
|
||||||
|
error: Error | string,
|
||||||
|
status: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
output: 'json' | 'html' | 'auto' = 'auto'
|
||||||
|
): ErrorResponseFactory {
|
||||||
|
if ( typeof error === 'string' ) error = new Error(error)
|
||||||
|
return new ErrorResponseFactory(error, status, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorResponseFactory extends ResponseFactory {
|
||||||
|
protected targetMode: 'json' | 'html' | 'auto' = 'auto'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly error: Error,
|
||||||
|
status: HTTPStatus,
|
||||||
|
output: 'json' | 'html' | 'auto' = 'auto'
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.status(status)
|
||||||
|
this.mode(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
public mode(output: 'json' | 'html' | 'auto'): ErrorResponseFactory {
|
||||||
|
this.targetMode = output
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(request: Request) {
|
||||||
|
request = await super.write(request)
|
||||||
|
const wants = request.wants()
|
||||||
|
|
||||||
|
if ( this.targetMode === 'json' || (this.targetMode === 'auto' && wants === 'json') ) {
|
||||||
|
request.response.setHeader('Content-Type', 'application/json')
|
||||||
|
request.response.body = this.buildJSON(this.error)
|
||||||
|
} else if ( this.targetMode === 'html' || (this.targetMode === 'auto' && (wants === 'html' || wants === 'unknown')) ) {
|
||||||
|
request.response.setHeader('Content-Type', 'text/html')
|
||||||
|
request.response.body = this.buildHTML(this.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME XML support
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the HTML display for the given error.
|
||||||
|
* @param {Error} error
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected buildHTML(error: Error) {
|
||||||
|
let context: any
|
||||||
|
if ( error instanceof ErrorWithContext ) {
|
||||||
|
context = error.context
|
||||||
|
if ( error.originalError ) {
|
||||||
|
error = error.originalError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let str = `
|
||||||
|
<b>Sorry, an unexpected error occurred while processing your request.</b>
|
||||||
|
<br>
|
||||||
|
<pre><code>
|
||||||
|
Name: ${error.name}
|
||||||
|
Message: ${error.message}
|
||||||
|
Stack trace:
|
||||||
|
- ${error.stack ? error.stack.split(/\s+at\s+/).slice(1).join('<br> - ') : 'none'}
|
||||||
|
</code></pre>
|
||||||
|
`
|
||||||
|
|
||||||
|
if ( context && typeof context === 'object' ) {
|
||||||
|
str += `
|
||||||
|
<pre><code>
|
||||||
|
Context:
|
||||||
|
${Object.keys(context).map(key => ` - ${key} : ${context[key]}`)}
|
||||||
|
</code></pre>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
protected buildJSON(error: Error) {
|
||||||
|
return JSON.stringify(api.error(error))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import {ResponseFactory} from "./ResponseFactory";
|
||||||
|
import {Request} from "../lifecycle/Request";
|
||||||
|
|
||||||
|
export function html(value: string): HTMLResponseFactory {
|
||||||
|
return new HTMLResponseFactory(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HTMLResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly value: string,
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
public async write(request: Request) {
|
||||||
|
request = await super.write(request)
|
||||||
|
request.response.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||||
|
request.response.body = this.value
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import {ErrorResponseFactory} from "./ErrorResponseFactory";
|
||||||
|
import {HTTPError} from "../HTTPError";
|
||||||
|
import {HTTPStatus} from "@extollo/util"
|
||||||
|
|
||||||
|
export function http(status: HTTPStatus, message?: string, output: 'json' | 'html' | 'auto' = 'auto'): HTTPErrorResponseFactory {
|
||||||
|
return new HTTPErrorResponseFactory(new HTTPError(status, message), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HTTPErrorResponseFactory extends ErrorResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly error: HTTPError,
|
||||||
|
output: 'json' | 'html' | 'auto' = 'auto', // FIXME xml support
|
||||||
|
) {
|
||||||
|
super(error, error.status, output)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import {ResponseFactory} from "./ResponseFactory";
|
||||||
|
import {Request} from "../lifecycle/Request";
|
||||||
|
|
||||||
|
export function json(value: any): JSONResponseFactory {
|
||||||
|
return new JSONResponseFactory(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JSONResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly value: any
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
public async write(request: Request) {
|
||||||
|
request = await super.write(request)
|
||||||
|
request.response.setHeader('Content-Type', 'application/json')
|
||||||
|
request.response.body = JSON.stringify(this.value)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import {HTTPStatus} from "@extollo/util"
|
||||||
|
import {Instantiable} from "@extollo/di"
|
||||||
|
import {Request} from "../lifecycle/Request"
|
||||||
|
|
||||||
|
export abstract class ResponseFactory {
|
||||||
|
protected targetStatus: HTTPStatus = HTTPStatus.OK
|
||||||
|
|
||||||
|
public async write(request: Request): Promise<Request> {
|
||||||
|
request.response.setStatus(this.targetStatus)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
public status(status: HTTPStatus) {
|
||||||
|
this.targetStatus = status
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import {ResponseFactory} from "./ResponseFactory";
|
||||||
|
import {Request} from "../lifecycle/Request";
|
||||||
|
|
||||||
|
export function plaintext(value: string): StringResponseFactory {
|
||||||
|
return new StringResponseFactory(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StringResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly value: string,
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
public async write(request: Request) {
|
||||||
|
request = await super.write(request)
|
||||||
|
request.response.setHeader('Content-Type', 'text/plain')
|
||||||
|
request.response.body = this.value
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import {ResponseFactory} from "./ResponseFactory";
|
||||||
|
import {HTTPStatus} from "@extollo/util";
|
||||||
|
import {Request} from "../lifecycle/Request";
|
||||||
|
|
||||||
|
export function redirect(destination: string): TemporaryRedirectResponseFactory {
|
||||||
|
return new TemporaryRedirectResponseFactory(destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemporaryRedirectResponseFactory extends ResponseFactory {
|
||||||
|
protected targetStatus: HTTPStatus = HTTPStatus.TEMPORARY_REDIRECT
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly destination: string
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
public async write(request: Request) {
|
||||||
|
request = await super.write(request)
|
||||||
|
request.response.setHeader('Location', this.destination)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Base type for an API response format.
|
||||||
|
*/
|
||||||
|
export interface APIResponse {
|
||||||
|
success: boolean,
|
||||||
|
message?: string,
|
||||||
|
data?: any,
|
||||||
|
error?: {
|
||||||
|
name: string,
|
||||||
|
message: string,
|
||||||
|
stack?: string[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a mesage as a successful API response.
|
||||||
|
* @param {string} message
|
||||||
|
* @return APIResponse
|
||||||
|
*/
|
||||||
|
export function message(message: string): APIResponse {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a single record as a successful API response.
|
||||||
|
* @param record
|
||||||
|
* @return APIResponse
|
||||||
|
*/
|
||||||
|
export function one(record: any): APIResponse {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: record,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an array of records as a successful API response.
|
||||||
|
* @param {array} records
|
||||||
|
* @return APIResponse
|
||||||
|
*/
|
||||||
|
export function many(records: any[]): APIResponse {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
records,
|
||||||
|
total: records.length,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an error message or Error instance as an API response.
|
||||||
|
* @param {string|Error} error
|
||||||
|
* @return APIResponse
|
||||||
|
*/
|
||||||
|
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) : [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue