import type { ZodTuple } from 'zod'; import type { CallMessage } from '../messages/CallMessage'; import type { CallAnswerMessage } from '../messages/CallAnswerMessage'; import { AnswerError } from '../messages/AnswerError'; import type { ActionInstance } from './ActionInstance'; // import { createCallAnswerMessage } from './utils/createCallAnswerMessage'; import { ActionBox } from './ActionBox'; import { Context } from './Context'; import { getContext } from './utils/createActionProxy'; import { ActionConfig } from './ActionConfig'; import { MethodConfig } from './MethodConfig'; import { isZodObject } from './utils/isZodObject'; import { isZodTuple } from './utils/isZodTuple'; export class CallController { public readonly method: string; public readonly args: Array; public readonly actionConfig: ActionConfig; public readonly methodConfig?: MethodConfig; public context!: Context; public instance!: ActionInstance; private messageTemplate: CallAnswerMessage = { transactionId: '', description: '', type: 'call:answer', // context: { ns: {}, actions: {} }, success: true, result: null, }; constructor(public readonly message: CallMessage, public readonly actionBox: ActionBox) { const { config } = actionBox; const { messageTemplate } = this; this.method = message.call.method; this.args = message.call.args; this.actionConfig = config; if (config.methods[this.method]) { this.methodConfig = config.methods[this.method]; } messageTemplate.description = `CallAnswer: ${this.actionBox.name}.${this.method}(...)`; messageTemplate.transactionId = message.transactionId; this.checkAvailable(); } public setActionProxy(ap: any) { this.instance = ap; this.context = getContext(ap); } public async call(): Promise { try { await this.context.setIncomingContext(this.message.context); await this.fireOnBeforeCall(); const args = await this.parseArguments(); const result = await this.parseResult( await this.instance[this.method](...args), ); return await this.createAnswerMessage(result); } catch (e) { return this.createErrorAnswerMessage(e); } } public async fireOnBeforeCall() { await this.actionConfig.hooks.onBeforeCall?.(this.context); await this.methodConfig?.hooks.onBeforeCall?.(this.context, this.message); } public createErrorAnswerMessage(result: any): CallAnswerMessage { return { ...this.messageTemplate, result, success: false }; } protected async createAnswerMessage(result: any): Promise { const context = await this.context.getOutgoingContext(); return { ...this.messageTemplate, result, context }; } protected async parseArguments(): Promise> { if (this.methodConfig?.paramsSchema) { const { paramsSchema } = this.methodConfig; try { if (isZodObject(paramsSchema)) { return Object.values(await paramsSchema.parseAsync({ ...this.args })); } if (isZodTuple(paramsSchema)) { return await (paramsSchema as ZodTuple).parseAsync(this.args); } } catch (e) { throw new AnswerError('ParamsSchemaError', e, 'Params do not match the declared scheme.'); } } return this.args; } protected async parseResult(result: any): Promise { if (this.methodConfig?.resultSchema) { const { resultSchema } = this.methodConfig; try { return await resultSchema.parseAsync(result); } catch (e) { throw new AnswerError('ResultSchemaError', e, 'Result do not match the declared scheme.'); } } return result; } protected checkAvailable() { if (!this.methodConfig) { // eslint-disable-next-line @typescript-eslint/no-throw-literal throw this.createErrorAnswerMessage( new Error(`The "${this.method}" method in "${this.actionBox.name}" action is not available.`), ); } } }