# frozen_string_literal: true require_relative "../output/output" require_relative "../logging/logger" require_relative "../helpers/input_output_pair" module Kanal module Core module Helpers # # This class executes response block and constructs output # It is also can execute response block and disable moving it forward to the # queue # class ResponseExecutionBlock include Output include Logging::Logger attr_reader :response_block, :input # # Creating response execution block with all needed parameters # # @param response_block [Kanal::Core::Helpers::ResponseBlock] response block # @param input [Kanal::Core::Input::Input] input provided by router # @param default_error_node [Kanal::Core::Router::RouterNode] default error node if no user defined error node available # @param error_node [Kanal::Core::Router::RouterNode] user (developer of bot) defined error node with information about error # def initialize(response_block, input, default_error_node, error_node) @response_block = response_block @input = input @default_error_node = default_error_node @error_node = error_node end # # Execute response block and ship created output into the queue # # @param core [Kanal::Core::Core] core to access parameter registrators and stuff TODO: remove core here and provide registrator as dependency # @param input_output_pair_queue [Kanal::Core::Helpers::Queue] queue where Kanal::Core::Helpers::InputOutputPair will be stored # # @return [void] # def execute(core, input_output_pair_queue) if response_block.async? # NOTE: Thread doesnt just die here - it's execution is continued in input_output_pair_queue.enqueue in router # then :item_queued hook is called inside and subsequently output_ready_block gets called in this thread # TODO: be aware that this can cause unexpected behaviour. Maybe think how to rework it. Thread.new do output = construct_output core unless output.canceled? input_output_pair_queue.enqueue InputOutputPair.new(@input, output) end end else output = construct_output core unless output.canceled? input_output_pair_queue.enqueue InputOutputPair.new(@input, output) end end end private # # Construct output from response block # # @param core [Kanal::Core::Core] core to access registrator # # @return [Kanal::Core::Output::Output] output created # def construct_output(core) logger.debug "Constructing output for input ##{input.__id__}" output = Output::Output.new core.output_parameter_registrator, @input, core # Canceling output to be processed further if response block is functional. # Whole .functional thing is done to allow after_respond and after_respond_async blocks # that should not spawn outputs output.cancel if @response_block.functional? begin core.hooks.call :output_just_created, input, output # Evaluating response block output.instance_eval(&@response_block.block) rescue => e # Something wrong happened # we try and construct error response provided by the user (dev created bot) # if there is no any - we use default user response block logger.error "Failed to construct output for input ##{input.__id__}. Error: '#{e}'" output = Output::Output.new core.output_parameter_registrator, @input, core error_node = @error_node || @default_error_node logger.debug "Trying to construct error response for input ##{input.__id__}. Error response is default: #{@error_node.nil?}" begin output.instance_eval(&error_node.response_blocks.first.block) rescue => e # Something wrong happened in user defined error response or # for some reason even default error response block failed # Logging error and trying default error block logger.error "Failed to construct error response for input ##{input.__id__}. Error: '#{e}'" logger.debug "Trying to construct default error response for input ##{input.__id__}" output.instance_eval(&@default_error_node.response_blocks.first.block) end end logger.debug "Output ##{output.__id__} for input ##{input.__id__} constructed" output end end end end end