# frozen_string_literal: true require_relative "hook_event" module FiniteMachine # Module responsible for safety checks against known methods module Safety EVENT_CONFLICT_MESSAGE = \ "You tried to define an event named \"%{name}\", however this would " \ "generate \"%{type}\" method \"%{method}\", which is already defined " \ "by %{source}" STATE_CALLBACK_CONFLICT_MESSAGE = \ "\"%{type}\" callback is a state listener and cannot be used " \ "with \"%{name}\" event name. Please use on_before or on_after instead." EVENT_CALLBACK_CONFLICT_MESSAGE = \ "\"%{type}\" callback is an event listener and cannot be used " \ "with \"%{name}\" state name. Please use on_enter, on_transition or " \ "on_exit instead." CALLBACK_INVALID_MESSAGE = \ "\"%{name}\" is not a valid callback name. " \ "Valid callback names are \"%{callbacks}" # Raise error when the method is already defined # # @example # detect_event_conflict!(:test, "test=") # # @raise [FiniteMachine::AlreadyDefinedError] # # @return [nil] # # @api public def detect_event_conflict!(event_name, method_name = event_name) if method_already_implemented?(method_name) raise FiniteMachine::AlreadyDefinedError, EVENT_CONFLICT_MESSAGE % { name: event_name, type: :instance, method: method_name, source: "FiniteMachine" } end end # Raise error when the callback name is not valid # # @example # ensure_valid_callback_name!(HookEvent::Enter, ":state_name") # # @raise [FiniteMachine::InvalidCallbackNameError] # # @return [nil] # # @api public def ensure_valid_callback_name!(event_type, name) message = if wrong_event_name?(name, event_type) EVENT_CALLBACK_CONFLICT_MESSAGE % { type: "on_#{event_type}", name: name } elsif wrong_state_name?(name, event_type) STATE_CALLBACK_CONFLICT_MESSAGE % { type: "on_#{event_type}", name: name } elsif !callback_names.include?(name) CALLBACK_INVALID_MESSAGE % { name: name, callbacks: callback_names.to_a.inspect } else nil end message && raise_invalid_callback_error(message) end private # Check if event name exists # # @param [Symbol] name # # @param [FiniteMachine::HookEvent] event_type # # @return [Boolean] # # @api private def wrong_event_name?(name, event_type) machine.states.include?(name) && !machine.events.include?(name) && event_type < HookEvent::Anyaction end # Check if state name exists # # @param [Symbol] name # # @param [FiniteMachine::HookEvent] event_type # # @return [Boolean] # # @api private def wrong_state_name?(name, event_type) machine.events.include?(name) && !machine.states.include?(name) && event_type < HookEvent::Anystate end def raise_invalid_callback_error(message) exception = InvalidCallbackNameError machine.catch_error(exception) || raise(exception, message) end # Check if method is already implemented inside StateMachine # # @param [String] name # the method name # # @return [Boolean] # # @api private def method_already_implemented?(name) method_defined_within?(name, FiniteMachine::StateMachine) end # Check if method is defined within a given class # # @param [String] name # the method name # # @param [Object] klass # # @return [Boolean] # # @api private def method_defined_within?(name, klass) klass.method_defined?(name) || klass.private_method_defined?(name) end end # Safety end # FiniteMachine