# frozen_string_literal: true module ConvenientService module Service module Plugins module CanHaveSteps module Entities ## # @internal # TODO: Extract `StepDefinition`. This way `has_organizer?` check can be avoided completely. # class Step module Concern module InstanceMethods include Support::Copyable include Support::Delegate ## # @return [Boolean] # delegate :success?, to: :result ## # @return [Boolean] # delegate :failure?, to: :result ## # @return [Boolean] # delegate :error?, to: :result ## # @return [Boolean] # delegate :not_success?, to: :result ## # @return [Boolean] # delegate :not_failure?, to: :result ## # @return [Boolean] # delegate :not_error?, to: :result ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result::Plugins::HasJSendStatusAndAttributes::Entities::Data] # delegate :data, to: :result ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result::Plugins::HasJSendStatusAndAttributes::Entities::Message] # delegate :message, to: :result ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result::Plugins::HasJSendStatusAndAttributes::Entities::Code] # delegate :code, to: :result ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result::Plugins::HasJSendStatusAndAttributes::Entities::Data] # delegate :unsafe_data, to: :result ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result::Plugins::HasJSendStatusAndAttributes::Entities::Message] # delegate :unsafe_message, to: :result ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result::Plugins::HasJSendStatusAndAttributes::Entities::Code] # delegate :unsafe_code, to: :result ## # @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Service] # # @internal # TODO: Return service instance to be compatible with result. # delegate :service, to: :params ## # @return [Array] # # @internal # TODO: Create dedicated classes for input and output methods. # delegate :outputs, to: :params ## # @return [Integer] # delegate :index, to: :params ## # @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Service] # delegate :container, to: :params ## # @return [Hash{Symbol => Object}] # delegate :extra_kwargs, to: :params ## # @param args [Array] # @param kwargs [Hash{Symbol => Object}] # @return [void] # def initialize(*args, **kwargs) @args = args @kwargs = kwargs end ## # @param other [Object] Can be any type. # @return [Boolean, nil] # # @internal # TODO: Currently `==` does NOT compare `extra_kwargs`. Is it OK? # def ==(other) return unless other.instance_of?(self.class) return false if service != other.service return false if inputs != other.inputs return false if outputs != other.outputs return false if index != other.index return false if container != other.container return false if organizer(raise_when_missing: false) != other.organizer(raise_when_missing: false) true end ## # @return [Boolean] # def has_organizer? Utils.to_bool(organizer(raise_when_missing: false)) end ## # @return [Boolean] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # def has_reassignment?(name) outputs.any? { |output| output.reassignment?(name) } end ## # @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Method, nil] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # def reassignment(name) outputs.find { |output| output.reassignment?(name) } end ## # @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Structs::Params] # def params @params ||= resolve_params end ## # @return [ConvenientService::Service] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # # @internal # NOTE: `raise_when_missing` option is inspired by `where.missing` in Rails. # - https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods/WhereChain.html#method-i-missing # - https://boringrails.com/tips/activerecord-where-missing-associations # def organizer(raise_when_missing: true) @organizer ||= params.organizer raise Exceptions::StepHasNoOrganizer.new(step: self) if @organizer.nil? && raise_when_missing @organizer end ## # @return [Array] # def inputs @inputs ||= params.inputs.map { |input| input.copy(overrides: {kwargs: {organizer: organizer(raise_when_missing: false)}}) } end ## # @return [Hash{Symbol => Object}] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # def input_values @input_values ||= calculate_input_values end ## # @return [Hash{Symbol => Object}] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # def output_values @output_values ||= result.unsafe_data.to_h.slice(*outputs.map(&:key).map(&:to_sym)) end ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # # @note `service_result` has middlewares. # # @internal # IMPORTANT: `service.result(**input_values)` is the reason, why services should have only kwargs as arguments. # # NOTE: `service_result` returns a foreign result that is later converted to own result by `convert_to_step_result` in `result`. # def service_result service.klass.result(**input_values) end ## # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # # @note `result` has middlewares. # # @internal # NOTE: Convents a foreign result received from `service_result` to own result. # def result convert_to_step_result(service_result) end ## # @return [String] # def printable_service service.klass.to_s end ## # @return [Class] # def service_class service.klass end ## # @return [void] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # def trigger_callback organizer.step(index) end ## # @return [void] # @raise [ConvenientService::Error] # def validate! inputs.each { |input| input.validate_as_input_for_container!(container) } outputs.each { |output| output.validate_as_output_for_container!(container) } true end ## # @return [void] # def define! outputs.each { |output| output.define_output_in_container!(container, index: index) } true end ## # @return [Array] # def to_args to_arguments.args end ## # @return [Hash{Symbol => Object}] # def to_kwargs to_arguments.kwargs end ## # @return [ConveninentService::Support::Arguments] # def to_arguments Support::Arguments.new(service, **kwargs.merge(in: inputs, out: outputs, index: index, container: container, organizer: organizer(raise_when_missing: false))) end private ## # @!attribute [r] args # @return [Array] # attr_reader :args ## # @!attribute [r] kwargs # @return [Hash] # attr_reader :kwargs ## # @return [Hash{Symbol => Object}] # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer] # # @internal # TODO: Commands instead of private methods. # def calculate_input_values inputs.reduce({}) { |values, input| values.merge(input.key.to_sym => input.value) } end ## # @param result [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result] # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result] # def convert_to_step_result(result) result.copy(overrides: {kwargs: {step: self, service: organizer}}) end ## # @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Structs::Params] # def resolve_params original_params = Commands::ExtractParams.call(args: args, kwargs: kwargs) Commands::CastParams.call(original_params: original_params) end end end end end end end end end