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.
137 lines
3.7 KiB
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) : [],
|
|
},
|
|
}
|
|
}
|
|
}
|