diff --git a/lib/floe.rb b/lib/floe.rb index bef087fe..d1ed95f6 100644 --- a/lib/floe.rb +++ b/lib/floe.rb @@ -43,8 +43,17 @@ module Floe class Error < StandardError; end class InvalidWorkflowError < Error; end class InvalidExecutionInput < Error; end - class PathError < Error; end - class ExecutionError < Error; end + + class ExecutionError < Error + attr_reader :floe_error + + def initialize(message, floe_error = "States.Runtime") + super(message) + @floe_error = floe_error + end + end + + class PathError < ExecutionError; end def self.logger @logger ||= NullLogger.new diff --git a/lib/floe/validation_mixin.rb b/lib/floe/validation_mixin.rb index 6e46a65c..120ff5f7 100644 --- a/lib/floe/validation_mixin.rb +++ b/lib/floe/validation_mixin.rb @@ -18,8 +18,8 @@ def invalid_field_error!(field_name, field_value = nil, comment = nil) self.class.invalid_field_error!(name, field_name, field_value, comment) end - def runtime_field_error!(field_name, field_value, comment) - raise Floe::ExecutionError, self.class.field_error_text(name, field_name, field_value, comment) + def runtime_field_error!(field_name, field_value, comment, floe_error: "States.Runtime") + raise Floe::ExecutionError.new(self.class.field_error_text(name, field_name, field_value, comment), floe_error) end def workflow_state?(field_value, workflow) diff --git a/lib/floe/workflow/state.rb b/lib/floe/workflow/state.rb index 477241eb..5c972455 100644 --- a/lib/floe/workflow/state.rb +++ b/lib/floe/workflow/state.rb @@ -48,7 +48,7 @@ def run_nonblock!(context) return Errno::EAGAIN unless ready?(context) finish(context) - rescue Floe::Error => e + rescue Floe::ExecutionError => e mark_error(context, e) end @@ -82,7 +82,7 @@ def mark_finished(context) def mark_error(context, exception) # InputPath or OutputPath were bad. context.next_state = nil - context.output = {"Error" => "States.Runtime", "Cause" => exception.message} + context.output = {"Error" => exception.floe_error, "Cause" => exception.message} # Since finish threw an exception, super was never called. Calling that now. mark_finished(context) end diff --git a/lib/floe/workflow/states/choice.rb b/lib/floe/workflow/states/choice.rb index 89e28af8..11780ab8 100644 --- a/lib/floe/workflow/states/choice.rb +++ b/lib/floe/workflow/states/choice.rb @@ -23,6 +23,7 @@ def finish(context) output = output_path.value(context, input) next_state = choices.detect { |choice| choice.true?(context, output) }&.next || default + runtime_field_error!("Default", nil, "not defined and no match found", :floe_error => "States.NoChoiceMatched") if next_state.nil? context.next_state = next_state context.output = output super diff --git a/spec/workflow/states/choice_spec.rb b/spec/workflow/states/choice_spec.rb index a2da6b13..16cb5c30 100644 --- a/spec/workflow/states/choice_spec.rb +++ b/spec/workflow/states/choice_spec.rb @@ -74,5 +74,35 @@ expect(ctx.next_state).to eq("DefaultState") end end + + context "with no default" do + let(:payload) do + { + "Choice1" => {"Type" => "Choice", "Choices" => choices}, + "FirstMatchState" => {"Type" => "Succeed"}, + "SecondMatchState" => {"Type" => "Succeed"} + } + end + + context "with an input value matching a condition" do + let(:input) { {"foo" => 1} } + + it "returns the next state" do + state.run_nonblock!(ctx) + expect(ctx.next_state).to eq("FirstMatchState") + end + end + + context "with an input value not matching a condition" do + let(:input) { {"foo" => 3} } + + it "throws error when not found" do + workflow.run_nonblock + expect(ctx.failed?).to eq(true) + expect(ctx.output["Error"]).to eq("States.NoChoiceMatched") + expect(ctx.output["Cause"]).to eq("States.Choice1 field \"Default\" not defined and no match found") + end + end + end end end