lib/floe/workflow/intrinsic_function/transformer.rb in floe-0.12.0 vs lib/floe/workflow/intrinsic_function/transformer.rb in floe-0.13.0
- old
+ new
@@ -7,10 +7,11 @@
module Floe
class Workflow
class IntrinsicFunction
class Transformer < Parslet::Transform
OptionalArg = Struct.new(:type)
+ VariadicArgs = Struct.new(:type)
class << self
def process_args(args, function, signature = nil)
args = resolve_args(args)
if signature
@@ -35,24 +36,37 @@
end
end
def check_arity(args, function, signature)
if signature.any?(OptionalArg)
- signature_without_optional = signature.reject { |a| a.kind_of?(OptionalArg) }
- signature_size = (signature_without_optional.size..signature.size)
+ signature_required = signature.reject { |a| a.kind_of?(OptionalArg) }
+ signature_size = (signature_required.size..signature.size)
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature_size})" unless signature_size.include?(args.size)
+ elsif signature.any?(VariadicArgs)
+ signature_required = signature.reject { |a| a.kind_of?(VariadicArgs) }
+
+ raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected at least #{signature_required.size})" unless args.size >= signature_required.size
else
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature.size})" unless signature.size == args.size
end
end
def check_types(args, function, signature)
+ # Adjust the signature for VariadicArgs to create a copy of the expected type for each given arg
+ if signature.last.kind_of?(VariadicArgs)
+ signature = signature[0..-2] + Array.new(args.size - signature.size + 1, signature.last.type)
+ end
+
args.zip(signature).each_with_index do |(arg, type), index|
type = type.type if type.kind_of?(OptionalArg)
- raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected #{type})" unless arg.kind_of?(type)
+ if type.kind_of?(Array)
+ raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected one of #{type.join(", ")})" unless type.any? { |t| arg.kind_of?(t) }
+ else
+ raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected #{type})" unless arg.kind_of?(type)
+ end
end
end
end
rule(:null_literal => simple(:v)) { nil }
@@ -61,10 +75,54 @@
rule(:string => simple(:v)) { v.to_s[1..-2] }
rule(:number => simple(:v)) { v.match(/[eE.]/) ? Float(v) : Integer(v) }
rule(:jsonpath => simple(:v)) { Floe::Workflow::Path.value(v.to_s, context, input) }
+ STATES_FORMAT_PLACEHOLDER = /(?<!\\)\{\}/.freeze
+
+ rule(:states_format => {:args => subtree(:args)}) do
+ args = Transformer.process_args(args(), "States.Format", [String, VariadicArgs[[String, TrueClass, FalseClass, Integer, Float, NilClass]]])
+ str, *rest = *args
+
+ # TODO: Handle templates with escaped characters, including invalid templates
+ # See https://states-language.net/#intrinsic-functions (number 6)
+
+ expected_args = str.scan(STATES_FORMAT_PLACEHOLDER).size
+ actual_args = rest.size
+ if expected_args != actual_args
+ raise ArgumentError, "number of arguments to States.Format do not match the occurrences of {} (given #{actual_args}, expected #{expected_args})"
+ end
+
+ rest.each do |arg|
+ str = str.sub(STATES_FORMAT_PLACEHOLDER, arg.nil? ? "null" : arg.to_s)
+ end
+
+ # TODO: Handle arguments that have escape characters within them but are interpolated
+ str.gsub!("\\'", "'")
+ str.gsub!("\\{", "{")
+ str.gsub!("\\}", "}")
+ str.gsub!("\\\\", "\\")
+
+ str
+ end
+
+ rule(:states_json_to_string => {:args => subtree(:args)}) do
+ args = Transformer.process_args(args(), "States.JsonToString", [Object])
+ json = args.first
+
+ JSON.generate(json)
+ end
+
+ rule(:states_string_to_json => {:args => subtree(:args)}) do
+ args = Transformer.process_args(args(), "States.StringToJson", [String])
+ str = args.first
+
+ JSON.parse(str)
+ rescue JSON::ParserError => e
+ raise ArgumentError, "invalid value for argument 1 to States.StringToJson (invalid json: #{e.message})"
+ end
+
rule(:states_array => {:args => subtree(:args)}) do
Transformer.process_args(args, "States.Array")
end
rule(:states_array_partition => {:args => subtree(:args)}) do
@@ -133,11 +191,11 @@
end
rule(:states_hash => {:args => subtree(:args)}) do
args = Transformer.process_args(args(), "States.Hash", [Object, String])
data, algorithm = *args
- raise NotImplementedError if data.kind_of?(Hash)
+
if data.nil?
raise ArgumentError, "invalid value for argument 1 to States.Hash (given null, expected non-null)"
end
algorithms = %w[MD5 SHA-1 SHA-256 SHA-384 SHA-512]
@@ -145,11 +203,23 @@
raise ArgumentError, "invalid value for argument 2 to States.Hash (given #{algorithm.inspect}, expected one of #{algorithms.map(&:inspect).join(", ")})"
end
require "openssl"
algorithm = algorithm.sub("-", "")
- data = data.to_json unless data.kind_of?(String)
- OpenSSL::Digest.hexdigest(algorithm, data)
+ data = JSON.generate(data) unless data.kind_of?(String)
+ OpenSSL::Digest.hexdigest(algorithm, data).force_encoding("UTF-8")
+ end
+
+ rule(:states_json_merge => {:args => subtree(:args)}) do
+ args = Transformer.process_args(args(), "States.JsonMerge", [Hash, Hash, [TrueClass, FalseClass]])
+ left, right, deep = *args
+
+ if deep
+ # NOTE: not implemented by AWS Step Functions and nuances not defined in docs
+ left.merge(right) { |_key, l, r| l.kind_of?(Hash) && r.kind_of?(Hash) ? l.merge(r) : r }
+ else
+ left.merge(right)
+ end
end
rule(:states_math_random => {:args => subtree(:args)}) do
args = Transformer.process_args(args(), "States.MathRandom", [Integer, Integer, OptionalArg[Integer]])
range_start, range_end, seed = *args