From 0993b7769d2475fd29faebfc084ac70161421997 Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Thu, 23 May 2024 00:42:23 -0400 Subject: [PATCH] introduce binary choice rules binary and unary are under choice_rule These are not children of Data so Data can reference the binary and unary classes --- lib/floe.rb | 6 ++ lib/floe/workflow/choice_rule.rb | 21 ++++- lib/floe/workflow/choice_rule/data.rb | 92 +++++-------------- lib/floe/workflow/choice_rule/equals.rb | 15 +++ lib/floe/workflow/choice_rule/greater_than.rb | 15 +++ .../choice_rule/greater_than_equals.rb | 15 +++ lib/floe/workflow/choice_rule/less_than.rb | 15 +++ .../workflow/choice_rule/less_than_equals.rb | 15 +++ lib/floe/workflow/choice_rule/matches.rb | 21 +++++ 9 files changed, 144 insertions(+), 71 deletions(-) create mode 100644 lib/floe/workflow/choice_rule/equals.rb create mode 100644 lib/floe/workflow/choice_rule/greater_than.rb create mode 100644 lib/floe/workflow/choice_rule/greater_than_equals.rb create mode 100644 lib/floe/workflow/choice_rule/less_than.rb create mode 100644 lib/floe/workflow/choice_rule/less_than_equals.rb create mode 100644 lib/floe/workflow/choice_rule/matches.rb diff --git a/lib/floe.rb b/lib/floe.rb index 611376ef..39c7ae6d 100644 --- a/lib/floe.rb +++ b/lib/floe.rb @@ -19,6 +19,12 @@ require_relative "floe/workflow/choice_rule/is_present" require_relative "floe/workflow/choice_rule/is_string" require_relative "floe/workflow/choice_rule/is_timestamp" +require_relative "floe/workflow/choice_rule/equals" +require_relative "floe/workflow/choice_rule/greater_than" +require_relative "floe/workflow/choice_rule/greater_than_equals" +require_relative "floe/workflow/choice_rule/less_than_equals" +require_relative "floe/workflow/choice_rule/less_than" +require_relative "floe/workflow/choice_rule/matches" require_relative "floe/workflow/choice_rule/data" require_relative "floe/workflow/context" require_relative "floe/workflow/path" diff --git a/lib/floe/workflow/choice_rule.rb b/lib/floe/workflow/choice_rule.rb index c7241979..b09a2bc3 100644 --- a/lib/floe/workflow/choice_rule.rb +++ b/lib/floe/workflow/choice_rule.rb @@ -21,14 +21,20 @@ def build_children(sub_payloads) end end - attr_reader :next, :payload, :variable, :children + attr_reader :next, :payload, :variable + # used by Not, And, Or + attr_reader :children + # Used by binary for right hand side + attr_reader :ref, :ref_path - def initialize(payload, children = nil) + def initialize(payload, children = nil, ref: nil, ref_path: nil) @payload = payload @children = children @next = payload["Next"] @variable = payload["Variable"] + @ref = ref + @ref_path = ref_path end def true?(*) @@ -37,9 +43,20 @@ def true?(*) private + # used by unary and binary def variable_value(context, input) Path.value(variable, context, input) end + + # used by binary + def valid?(value) + !value.nil? + end + + # used by binary + def compare_value(context, input) + ref_path ? Path.value(ref_path, context, input) : ref + end end end end diff --git a/lib/floe/workflow/choice_rule/data.rb b/lib/floe/workflow/choice_rule/data.rb index ecff0b14..1ea183be 100644 --- a/lib/floe/workflow/choice_rule/data.rb +++ b/lib/floe/workflow/choice_rule/data.rb @@ -4,83 +4,37 @@ module Floe class Workflow class ChoiceRule class Data < Floe::Workflow::ChoiceRule - COMPARE_KEYS = %w[IsNull IsPresent IsNumeric IsString IsBoolean IsTimestamp String Numeric Boolean Timestamp].freeze DATA_RULES = { - "IsNull" => Floe::Workflow::ChoiceRule::IsNull, - "IsPresent" => Floe::Workflow::ChoiceRule::IsPresent, - "IsNumeric" => Floe::Workflow::ChoiceRule::IsNumeric, - "IsString" => Floe::Workflow::ChoiceRule::IsString, - "IsBoolean" => Floe::Workflow::ChoiceRule::IsBoolean, - "IsTimestamp" => Floe::Workflow::ChoiceRule::IsTimestamp, - } - - attr_reader :compare_key - attr_accessor :ref, :ref_path + "IsNull" => Floe::Workflow::ChoiceRule::IsNull, + "IsPresent" => Floe::Workflow::ChoiceRule::IsPresent, + "IsNumeric" => Floe::Workflow::ChoiceRule::IsNumeric, + "IsString" => Floe::Workflow::ChoiceRule::IsString, + "IsBoolean" => Floe::Workflow::ChoiceRule::IsBoolean, + "IsTimestamp" => Floe::Workflow::ChoiceRule::IsTimestamp, + "Equals" => Floe::Workflow::ChoiceRule::Data::Equals, + "LessThan" => Floe::Workflow::ChoiceRule::Data::LessThan, + "GreaterThan" => Floe::Workflow::ChoiceRule::Data::GreaterThan, + "LessThanEquals" => Floe::Workflow::ChoiceRule::Data::LessThanEquals, + "GreaterThanEquals" => Floe::Workflow::ChoiceRule::Data::GreaterThanEquals, + "Matches" => Floe::Workflow::ChoiceRule::Data::Matches + }.freeze + COMPARE_RULE = /^(String|Numeric|Boolean|Timestamp)?(#{DATA_RULES.keys.join("|")})(Path)?$/.freeze def self.build(payload) - compare_key = payload.keys.detect { |key| key.match?(/^(#{DATA_RULES.keys.join("|")})/) } - if compare_key - DATA_RULES[compare_key].new(payload) - else - Floe::Workflow::ChoiceRule::Data.new(payload) - end - end - - def initialize(*) - super - @compare_key = payload.keys.detect { |key| key.match?(/^(#{COMPARE_KEYS.join("|")})/) } - raise Floe::InvalidWorkflowError, "Data-test Expression Choice Rule must have a compare key" if @compare_key.nil? - - if @compare_key&.end_with?("Path") - @ref_path = payload[compare_key] - else - @ref = payload[compare_key] + compare_key, binary, klass, use_path = payload.keys.select do |key| + values = COMPARE_RULE.match(key) + break [key, values[1], DATA_RULES[values[2]], !!values[3]] if values end - end - - def true?(context, input) - lhs = variable_value(context, input) - rhs = compare_value(context, input) - return false unless valid?(lhs) + raise Floe::InvalidWorkflowError, "Data-test Expression Choice Rule must have a compare key" unless compare_key + raise Floe::InvalidWorkflowError, "Data-test Expression Choice Rule must have a valid compare key" unless klass - case compare_key - when "StringEquals", "StringEqualsPath", - "NumericEquals", "NumericEqualsPath", - "BooleanEquals", "BooleanEqualsPath", - "TimestampEquals", "TimestampEqualsPath" - lhs == rhs - when "StringLessThan", "StringLessThanPath", - "NumericLessThan", "NumericLessThanPath", - "TimestampLessThan", "TimestampLessThanPath" - lhs < rhs - when "StringGreaterThan", "StringGreaterThanPath", - "NumericGreaterThan", "NumericGreaterThanPath", - "TimestampGreaterThan", "TimestampGreaterThanPath" - lhs > rhs - when "StringLessThanEquals", "StringLessThanEqualsPath", - "NumericLessThanEquals", "NumericLessThanEqualsPath", - "TimestampLessThanEquals", "TimestampLessThanEqualsPath" - lhs <= rhs - when "StringGreaterThanEquals", "StringGreaterThanEqualsPath", - "NumericGreaterThanEquals", "NumericGreaterThanEqualsPath", - "TimestampGreaterThanEquals", "TimestampGreaterThanEqualsPath" - lhs >= rhs - when "StringMatches" - lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?')) + if binary + ref, ref_path = use_path ? [nil, payload[compare_key]] : [payload[compare_key], nil] + klass.new(payload, ref: ref, ref_path: ref_path) else - raise Floe::InvalidWorkflowError, "Invalid choice [#{compare_key}]" + klass.new(payload) end end - - private - - def valid?(value) - !value.nil? - end - - def compare_value(context, input) - @ref_path ? Path.value(@ref_path, context, input) : @ref - end end end end diff --git a/lib/floe/workflow/choice_rule/equals.rb b/lib/floe/workflow/choice_rule/equals.rb new file mode 100644 index 00000000..789f9288 --- /dev/null +++ b/lib/floe/workflow/choice_rule/equals.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Floe + class Workflow + class ChoiceRule + class Equals < Floe::Workflow::ChoiceRule + def true?(context, input) + lhs = variable_value(context, input) + rhs = compare_value(context, input) + valid?(lhs) && lhs == rhs + end + end + end + end +end diff --git a/lib/floe/workflow/choice_rule/greater_than.rb b/lib/floe/workflow/choice_rule/greater_than.rb new file mode 100644 index 00000000..afd280ac --- /dev/null +++ b/lib/floe/workflow/choice_rule/greater_than.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Floe + class Workflow + class ChoiceRule + class GreaterThan < Floe::Workflow::ChoiceRule + def true?(context, input) + lhs = variable_value(context, input) + rhs = compare_value(context, input) + valid?(lhs) && lhs > rhs + end + end + end + end +end diff --git a/lib/floe/workflow/choice_rule/greater_than_equals.rb b/lib/floe/workflow/choice_rule/greater_than_equals.rb new file mode 100644 index 00000000..55dcaff0 --- /dev/null +++ b/lib/floe/workflow/choice_rule/greater_than_equals.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Floe + class Workflow + class ChoiceRule + class GreaterThanEquals < Floe::Workflow::ChoiceRule + def true?(context, input) + lhs = variable_value(context, input) + rhs = compare_value(context, input) + valid?(lhs) && lhs >= rhs + end + end + end + end +end diff --git a/lib/floe/workflow/choice_rule/less_than.rb b/lib/floe/workflow/choice_rule/less_than.rb new file mode 100644 index 00000000..fd93c701 --- /dev/null +++ b/lib/floe/workflow/choice_rule/less_than.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Floe + class Workflow + class ChoiceRule + class LessThan < Floe::Workflow::ChoiceRule + def true?(context, input) + lhs = variable_value(context, input) + rhs = compare_value(context, input) + valid?(lhs) && lhs < rhs + end + end + end + end +end diff --git a/lib/floe/workflow/choice_rule/less_than_equals.rb b/lib/floe/workflow/choice_rule/less_than_equals.rb new file mode 100644 index 00000000..b2f93541 --- /dev/null +++ b/lib/floe/workflow/choice_rule/less_than_equals.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Floe + class Workflow + class ChoiceRule + class LessThanEquals < Floe::Workflow::ChoiceRule + def true?(context, input) + lhs = variable_value(context, input) + rhs = compare_value(context, input) + valid?(lhs) && lhs <= rhs + end + end + end + end +end diff --git a/lib/floe/workflow/choice_rule/matches.rb b/lib/floe/workflow/choice_rule/matches.rb new file mode 100644 index 00000000..4927031b --- /dev/null +++ b/lib/floe/workflow/choice_rule/matches.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Floe + class Workflow + class ChoiceRule + class Matches < Floe::Workflow::ChoiceRule + def initialize(payload, *, **) + super + # Note: only StringMatches exists (so no Path option) + # Since this is static, we're converting it up front + @ref = Regexp.escape(@ref).gsub('\*', '.*?') + end + + def true?(context, input) + lhs = variable_value(context, input) + valid?(lhs) && lhs.match?(ref) + end + end + end + end +end