diff --git a/lib/trailblazer/activity/circuit.rb b/lib/trailblazer/activity/circuit.rb index 470c0f23..44b90367 100644 --- a/lib/trailblazer/activity/circuit.rb +++ b/lib/trailblazer/activity/circuit.rb @@ -51,7 +51,16 @@ def call(args, start_task: @start_task, runner: Run, **circuit_options) # Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend. return [ last_signal, args ] if @stop_events.include?(task) # DISCUSS: return circuit_options here? - task = next_for(task, last_signal) or raise IllegalSignalError.new("<#{@name}>[#{task}][ #{last_signal.inspect} ]") + if (next_task = next_for(task, last_signal)) + task = next_task + else + raise IllegalSignalError.new( + task, + signal: last_signal, + outputs: @map[task], + exec_context: circuit_options[:exec_context], # passed at run-time from DSL + ) + end end end @@ -67,7 +76,26 @@ def next_for(last_task, signal) outputs[signal] end + # Common reasons to raise IllegalSignalError are + # * Returning invalid signal from custom Macros + # * Returning invalid signal from steps which are not taskWrapped, for example: `step task: method(:validate)` + # + # Rest assured, it won't be raised in case of below scenarios where they can return any value, + # * Steps with instance method signature, for example, `step :load_user` + # * Steps with proc signature, for example `step ->(ctx, **){}` class IllegalSignalError < RuntimeError + attr_reader :task, :signal + + def initialize(task, signal:, outputs:, exec_context:) + @task = task + @signal = signal + + message = "#{exec_context.class}: \n\t" \ + "\sUnrecognized Signal `#{signal.inspect}` returned from #{task.inspect}. Registered signals are, \n" \ + "- #{outputs.keys.join("\n- ")}" + + super(message) + end end end end diff --git a/test/circuit_test.rb b/test/circuit_test.rb index 8f26eafc..f598ddef 100644 --- a/test/circuit_test.rb +++ b/test/circuit_test.rb @@ -95,4 +95,34 @@ class CircuitTest < Minitest::Spec expect(j).must_equal 2 expect(bla).must_equal [] end + + let(:wicked_circuit) do + map = { + Start => { eureka: A }, + A => { "from a" => End } + } + + Trailblazer::Activity::Circuit.new( map, [ End ], start_task: Start ) + end + + it "throws an exception if any unknown signal is caught" do + DummyActivity = Class.new(Trailblazer::Activity) + + exception = assert_raises Trailblazer::Activity::Circuit::IllegalSignalError do + ctx = {} + flow_options = {} + circuit_options = { exec_context: DummyActivity.new(Hash.new) } + + wicked_circuit.([ ctx, flow_options ], **circuit_options) + end + + message = "CircuitTest::DummyActivity: \n\t" \ + "\sUnrecognized Signal `\"to a\"` returned from #{Start}. Registered signals are, \n" \ + "- eureka" + + assert_equal message, exception.message + + assert_equal Start, exception.task + assert_equal 'to a', exception.signal + end end