You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/http/response/api.ts

137 lines
3.7 KiB

/**
* Base type for an API response format.
*/
import {BaseSerializer, Event, ObjectSerializer} from '../../support/bus'
import {Awaitable, ErrorWithContext, hasOwnProperty, JSONState, uuid4} from '../../util'
export interface APIResponse<T> {
eventName?: string,
eventUuid?: string,
success: boolean,
message?: string,
data?: T,
error?: {
name: string,
message: string,
stack?: string[],
}
}
export function isAPIResponse(what: unknown): what is APIResponse<unknown> {
return typeof what === 'object' && what !== null
&& hasOwnProperty(what, 'success')
&& typeof what.success === 'boolean'
&& (!hasOwnProperty(what, 'message') || typeof what.message === 'string')
&& (!hasOwnProperty(what, 'error') || (
typeof what.error === 'object' && what.error !== null
&& hasOwnProperty(what.error, 'name') && typeof what.error.name === 'string'
&& hasOwnProperty(what.error, 'message') && typeof what.error.message === 'string'
&& (!hasOwnProperty(what.error, 'stack') || (
Array.isArray(what.error.stack) && what.error.stack.every(x => typeof x === 'string')
)
)
)
)
}
export function apiEvent<T>(response: APIResponse<T>): APIResponse<T> & Event {
if ( !response.eventName ) {
response.eventName = '@extollo/lib:APIResponse'
}
if ( !response.eventUuid ) {
response.eventUuid = uuid4()
}
return response as APIResponse<T> & Event
}
/**
* Serializer implementation that can encode/decode APIResponse objects.
*/
@ObjectSerializer()
export class APIResponseSerializer extends BaseSerializer<APIResponse<unknown>, JSONState> {
protected decodeSerial(serial: JSONState): Awaitable<APIResponse<unknown>> {
if ( isAPIResponse(serial) ) {
return serial
}
throw new ErrorWithContext('Could not decode API response: object is malformed')
}
protected encodeActual(actual: APIResponse<unknown>): Awaitable<JSONState> {
return actual as unknown as JSONState
}
protected getName(): string {
return '@extollo/lib:APIResponseSerializer'
}
matchActual(some: APIResponse<unknown>): boolean {
return isAPIResponse(some)
}
}
/**
* Formats a mesage as a successful API response.
* @param {string} displayMessage
* @return APIResponse
*/
export function message(displayMessage: string): APIResponse<void> {
return {
success: true,
message: displayMessage,
}
}
/**
* Formats a single record as a successful API response.
* @param record
* @return APIResponse
*/
export function one<T>(record: T): APIResponse<T> {
return {
success: true,
data: record,
}
}
/**
* Formats an array of records as a successful API response.
* @param {array} records
* @return APIResponse
*/
export function many<T>(records: T[]): APIResponse<{records: T[], total: number}> {
return {
success: true,
data: {
records,
total: records.length,
},
}
}
/**
* Formats an error message or Error instance as an API response.
* @return APIResponse
* @param thrownError
*/
export function error(thrownError: string | Error): APIResponse<undefined> {
if ( typeof thrownError === 'string' ) {
return {
success: false,
message: thrownError,
}
} else {
return {
success: false,
message: thrownError.message,
error: {
name: thrownError.name,
message: thrownError.message,
stack: thrownError.stack ? thrownError.stack.split(/\s+at\s+/).slice(1) : [],
},
}
}
}