Skip to content

Commit

Permalink
Validate that state machine input is valid JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
agrare committed Jun 21, 2024
1 parent 7eecf93 commit c5818ba
Show file tree
Hide file tree
Showing 14 changed files with 24 additions and 23 deletions.
1 change: 1 addition & 0 deletions lib/floe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
module Floe
class Error < StandardError; end
class InvalidWorkflowError < Error; end
class InvalidExecutionInput < Error; end

def self.logger
@logger ||= NullLogger.new
Expand Down
2 changes: 2 additions & 0 deletions lib/floe/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def run(args = ARGV)
end

workflows.all? { |workflow| workflow.context.success? }
rescue Floe::Error => err
abort(err.message)
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/floe/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def initialize(payload, context = nil, credentials = nil, name = nil)

@states = payload["States"].to_a.map { |state_name, state| State.build!(self, state_name, state) }
@states_by_name = @states.each_with_object({}) { |state, result| result[state.name] = state }
rescue Floe::InvalidWorkflowError
rescue Floe::InvalidWorkflowError, Floe::InvalidExecutionInput
raise
rescue => err
raise Floe::InvalidWorkflowError, err.message
Expand Down
6 changes: 2 additions & 4 deletions lib/floe/workflow/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ class Context
# @param input [Hash] (default: {})
def initialize(context = nil, input: nil, credentials: {})
context = JSON.parse(context) if context.kind_of?(String)

input ||= {}
input = JSON.parse(input) if input.kind_of?(String)
input = JSON.parse(input || "{}")

@context = context || {}
self["Execution"] ||= {}
Expand All @@ -23,7 +21,7 @@ def initialize(context = nil, input: nil, credentials: {})

@credentials = credentials || {}
rescue JSON::ParserError => err
raise Floe::InvalidWorkflowError, err.message
raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
end

def execution
Expand Down
6 changes: 3 additions & 3 deletions spec/workflow/context_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
RSpec.describe Floe::Workflow::Context do
let(:ctx) { described_class.new(:input => input) }
let(:ctx) { described_class.new(:input => input.to_json) }
let(:input) { {"x" => "y"}.freeze }

describe "#new" do
Expand All @@ -9,7 +9,7 @@
end

it "with a context, sets input and keeps context" do
ctx = described_class.new({"Execution" => {"api" => "http://localhost/"}}, :input => input)
ctx = described_class.new({"Execution" => {"api" => "http://localhost/"}}, :input => input.to_json)
expect(ctx.execution["api"]).to eq("http://localhost/")
expect(ctx.state).not_to eq(nil)
end
Expand All @@ -19,7 +19,7 @@
end

context "with a simple string input" do
let(:input) { "\"foo\"" }
let(:input) { "foo" }

it "sets the input" do
expect(ctx.execution["Input"]).to eq("foo")
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/retrier_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::Retrier do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:resource) { "docker://hello-world:latest" }
let(:workflow) do
make_workflow(
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/state_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::State do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:state) { workflow.start_workflow.current_state }
# picked a state that doesn't instantly finish
let(:workflow) { make_workflow(ctx, {"WaitState" => {"Type" => "Wait", "Seconds" => 1, "Next" => "SuccessState"}, "SuccessState" => {"Type" => "Succeed"}}) }
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/states/choice_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::States::Choice do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:state) { workflow.start_workflow.current_state }
let(:workflow) do
make_workflow(
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/states/fail_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::States::Fail do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:state) { workflow.start_workflow.current_state }
let(:workflow) do
make_workflow(
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/states/pass_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::States::Pass do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:state) { workflow.start_workflow.current_state }
let(:workflow) { make_workflow(ctx, payload) }
let(:payload) do
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/states/succeed_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::States::Succeed do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:state) { workflow.start_workflow.current_state }
let(:payload) { {"SuccessState" => {"Type" => "Succeed"}} }
let(:workflow) { make_workflow(ctx, payload) }
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/states/task_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::States::Task do
let(:input) { {"foo" => {"bar" => "baz"}, "bar" => {"baz" => "foo"}} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:resource) { "docker://hello-world:latest" }

describe "#run_async!" do
Expand Down
2 changes: 1 addition & 1 deletion spec/workflow/states/wait_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RSpec.describe Floe::Workflow::States::Pass do
let(:input) { {} }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }
let(:state) { workflow.start_workflow.current_state }
let(:workflow) { make_workflow(ctx, {"WaitState" => {"Type" => "Wait", "Seconds" => 1, "Next" => "SuccessState"}, "SuccessState" => {"Type" => "Succeed"}}) }

Expand Down
14 changes: 7 additions & 7 deletions spec/workflow_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
RSpec.describe Floe::Workflow do
let(:now) { Time.now.utc }
let(:input) { {"input" => "value"}.freeze }
let(:ctx) { Floe::Workflow::Context.new(:input => input) }
let(:ctx) { Floe::Workflow::Context.new(:input => input.to_json) }

describe "#new" do
it "sets initial state" do
Expand Down Expand Up @@ -57,7 +57,7 @@
it "raises an exception for invalid context" do
payload = make_payload({"FirstState" => {"Type" => "Success"}})

expect { described_class.new(payload, "invalid context") }.to raise_error(Floe::InvalidWorkflowError, "unexpected token at 'invalid context'")
expect { described_class.new(payload, "invalid context") }.to raise_error(Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: unexpected token at 'invalid context': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')")
end

it "raises an exception for invalid resource scheme in a Task state" do
Expand Down Expand Up @@ -334,25 +334,25 @@

describe ".wait" do
context "with two ready workflows" do
let(:workflow_1) { make_workflow(Floe::Workflow::Context.new(:input => input), {"FirstState" => {"Type" => "Succeed"}}) }
let(:workflow_2) { make_workflow(Floe::Workflow::Context.new(:input => input), {"FirstState" => {"Type" => "Succeed"}}) }
let(:workflow_1) { make_workflow(ctx, {"FirstState" => {"Type" => "Succeed"}}) }
let(:workflow_2) { make_workflow(ctx, {"FirstState" => {"Type" => "Succeed"}}) }

it "returns both workflows as ready to step" do
expect(described_class.wait([workflow_1, workflow_2], :timeout => 0)).to include(workflow_1, workflow_2)
end
end

context "with one ready workflow and one that would block" do
let(:workflow_1) { make_workflow(Floe::Workflow::Context.new(:input => input), {"FirstState" => {"Type" => "Succeed"}}) }
let(:workflow_2) { make_workflow(Floe::Workflow::Context.new(:input => input), {"FirstState" => {"Type" => "Wait", "Seconds" => 10, "End" => true}}).start_workflow.tap(&:step_nonblock) }
let(:workflow_1) { make_workflow(ctx, {"FirstState" => {"Type" => "Succeed"}}) }
let(:workflow_2) { make_workflow(ctx, {"FirstState" => {"Type" => "Wait", "Seconds" => 10, "End" => true}}).start_workflow.tap(&:step_nonblock) }

it "returns only the first workflow as ready to step" do
expect(described_class.wait([workflow_1, workflow_2], :timeout => 0)).to eq([workflow_1])
end
end

context "with a workflow that would block for 10 seconds" do
let(:workflow) { make_workflow(Floe::Workflow::Context.new(:input => input), {"FirstState" => {"Type" => "Wait", "Seconds" => 10, "End" => true}}).start_workflow.tap(&:step_nonblock) }
let(:workflow) { make_workflow(ctx, {"FirstState" => {"Type" => "Wait", "Seconds" => 10, "End" => true}}).start_workflow.tap(&:step_nonblock) }

it "returns no ready workflows with :timeout => 0" do
expect(described_class.wait(workflow, :timeout => 0)).to be_empty
Expand Down

0 comments on commit c5818ba

Please sign in to comment.