/** * 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 { 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 { 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(response: APIResponse): APIResponse & Event { if ( !response.eventName ) { response.eventName = '@extollo/lib:APIResponse' } if ( !response.eventUuid ) { response.eventUuid = uuid4() } return response as APIResponse & Event } /** * Serializer implementation that can encode/decode APIResponse objects. */ @ObjectSerializer() export class APIResponseSerializer extends BaseSerializer, JSONState> { protected decodeSerial(serial: JSONState): Awaitable> { if ( isAPIResponse(serial) ) { return serial } throw new ErrorWithContext('Could not decode API response: object is malformed') } protected encodeActual(actual: APIResponse): Awaitable { return actual as unknown as JSONState } protected getName(): string { return '@extollo/lib:APIResponseSerializer' } matchActual(some: APIResponse): boolean { return isAPIResponse(some) } } /** * Formats a mesage as a successful API response. * @param {string} displayMessage * @return APIResponse */ export function message(displayMessage: string): APIResponse { return { success: true, message: displayMessage, } } /** * Formats a single record as a successful API response. * @param record * @return APIResponse */ export function one(record: T): 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: 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 { 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) : [], }, } } }