Skip to content

Commit

Permalink
Merge pull request #267 from kbrock/choice_rule_payload_validation-no…
Browse files Browse the repository at this point in the history
…_default_choice

Support choices that do not have a Default defined
  • Loading branch information
agrare committed Aug 20, 2024
2 parents 64f03a2 + a832511 commit 5f3f1b1
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 4 deletions.
13 changes: 12 additions & 1 deletion lib/floe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@ module Floe
class Error < StandardError; end
class InvalidWorkflowError < Error; end
class InvalidExecutionInput < Error; end
class PathError < 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
Expand Down
10 changes: 9 additions & 1 deletion lib/floe/validation_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ 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, 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)
workflow.payload["States"] ? workflow.payload["States"].include?(field_value) : true
end
Expand All @@ -39,10 +43,14 @@ def missing_field_error!(name, field_name)
end

def invalid_field_error!(name, field_name, field_value, comment)
raise Floe::InvalidWorkflowError, field_error_text(name, field_name, field_value, comment)
end

def field_error_text(name, field_name, field_value, comment = nil)
# instead of displaying a large hash or array, just displaying the word Hash or Array
field_value = field_value.class if field_value.kind_of?(Hash) || field_value.kind_of?(Array)

parser_error!(name, "field \"#{field_name}\"#{" value \"#{field_value}\"" unless field_value.nil?} #{comment}")
"#{Array(name).join(".")} field \"#{field_name}\"#{" value \"#{field_value}\"" unless field_value.nil?} #{comment}"
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/floe/workflow/state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/floe/workflow/states/choice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions spec/workflow/states/choice_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 5f3f1b1

Please sign in to comment.